Goのビルド時間を条件同じにして計測してみた

以下の記事で、Intel MacとM1 Macでビルド時間が違うことに気づいたので、条件を同じにして比較。

https://isehara-3lv.sakura.ne.jp/blog/2023/04/23/dockerコンテナからイメージを作成する/

<時間計測の条件>

一度ビルドを完了した状態で、実行ファイルを削除して再度ビルドに要する時間を計測(実際行っていることはリンケージですね)

スクリプトファイルで、

% go build main.go

の前後のタイムスタンプで計測

M1 Mac native build time -> 2 seconds

 % ./build.sh 
2023年 4月24日 月曜日 13時12分41秒 JST
2023年 4月24日 月曜日 13時12分43秒 JST


M1 Mac Docker container build time -> 11 seconds

# ./build.sh
Mon Apr 24 05:00:53 BST 2023
Mon Apr 24 05:01:04 BST 2023


Intel mac Docker container build time -> 61 seconds

# ./build.sh
Mon Apr 24 04:18:53 BST 2023
Mon Apr 24 04:19:54 BST 2023


Raspberry Pi model B+ native build time -> 118 seconds

$ ./build.sh
Mon 24 Apr 13:05:53 JST 2023
Mon 24 Apr 13:07:51 JST 2023

 

Intel/M1 Macは他のタスクではほぼ同じパーフォーマンスなので、Intel CPUではarm CPUのエミュレーションが明らかに速度が遅いようで、M1 MacでのDockerだけがクロスビルドの許容内、規模にもよりますが。

 

admin

Wi-Fi無しのラズパイにWi-Fi機能追加

元々Linux用のUSBドングルでまともなものは無いのは知っているから、Wi-Fi機能のない初代に近いモデルにWi-Fi機能を追加するのはおそらくこれが一番手っ取り早い。

TP-LINKの携帯用のWi-Fiルーターを天板に両面テープで貼り付けただけ。元々このモデルはルーター/中継機/クライアントの三つのモードがあるので、クライアントモードを設定すれば良い。

 

電源にtype-b2本のUSB必要だけど、見た目一体感はありそう、ちょいダサいけど。

 

admin

DockerでラズパイのGolang build環境を作る

https://isehara-3lv.sakura.ne.jp/blog/2023/04/19/golangアプリは単純にクロスビルドしても動かない(db/

の対応としてとりあえずラズパイ自身でbuildさせましたが、ラズパイModel B+でbuildに必要な時間は、実行ファイルのタイムスタンプからおよそ二時間。これでは実用的ではないので代替え方法を考えるけれども、一番楽そうなのはDockerを使うことでしょう。

自分の環境を汚染させないとか、イメージの配布とかではなく、ラズパイの環境を作るための使用(DockerはM1 Macにインストール、ターゲットのコンテナはraspbian32 OS)です。

多少以前の記事になりますが、以下を参考に実行。raspbianイメージとかは最新版、と言っても2020年度が最新ですが。

https://www.koatech.info/blog/raspbian-on-docker/

・イメージ取得と作成

% wget http://ftp.jaist.ac.jp/pub/raspberrypi/raspios_lite_armhf/root.tar.xz

% docker image import ./root.tar.xz raspbian-stretch-lite:2020

・以下のコマンドでwarningが出る、とりあえずは無視して大丈夫な様子(QEMUは元々Intel CPU用だからか、Apple siliconでも今のところ何とかなってるけど)

% docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested

exec /register: exec format error

・コンテナ作成と起動(名前指定)

% docker run -it --name rasp32 raspbian-stretch-lite:2020 bash

・作成しているコンテナを起動するなら

% docker exec -it rasp32 bash

・Golang install

# wget https://golang.org/dl/go1.20.1.linux-armv6l.tar.gz

# sudo tar -C /usr/local -xzf go1.20.1.linux-armv6l.tar.gz

# /usr/local/go/bin/go version (パスが通っていない状態)

・Dockerのコンテナからファイルをクライアントにコピー(例)

% docker cp 21f2c6ce6eb8:/home/pi/hello .

・ディレクトリ指定すればその中全部をコピー(クライアントからコンテナへ)

% docker cp myfare 21f2c6ce6eb8:/home/pi/

コンテナ上のMacの該当ディレクトリを丸ごとコピー(gomodなど含めて)してきてbuild、できた実行ファイルをscpでラズパイに転送して起動するとちゃんと実行できました。実行ファイルの動作確認、最初は定番のハローワールドで確認しています。

エミュレーション(QEMU)なので、速度はMacでbuildするのに比較すると遥かに遅い、それでも二分ぐらいで終わっているからやはりラズパイで実行するよりはほぼ百倍速いから実用的です。

実行ファイルのサイズが微妙に違うのはライブラリの版数違いか、main1のほうがコンテナでbuildしたもの。

コンテナ上のraspbian、

ラズパイのbuild環境としてはおそらくデフォルトだと思う。カスタマイズしたコンテナを作る時にDcokerfileを記述しておけば、再現性確保できます。

 

admin

/etc/rc.localからsystemdになってました

ラズパイでmyfareアプリを起動時に立ち上げしようと思ってrc.localに記述しても起動しません。実は最近のLinuxのバージョンではrc.localは単にipアドレスの表示をするだけになっていて、アプリの起動はsystemdを使えということのようです。しかしsystemdに設定したつもりでも起動するとエラーになります。実行ファイルと同じディレクトリに配置しているファイルが見つからないと言われます。

$ sudo systemctl status myfare.service
● myfare.service - myfare
     Loaded: loaded (/etc/systemd/system/myfare.service; enabled; vendor preset>
     Active: failed (Result: exit-code) since Wed 2023-04-19 11:57:00 BST; 9s a>
    Process: 1805 ExecStart=/home/pi/myfare/main (code=exited, status=1/FAILURE)
   Main PID: 1805 (code=exited, status=1/FAILURE)
        CPU: 121ms

Apr 19 11:57:00 rasp-b systemd[1]: Started myfare.
Apr 19 11:57:00 rasp-b main[1805]: open uid.json: no such file or directory

事例検索して行き着いたのが、WorkingDirectory設定。

[Unit]
Description=myfare

[Service]
WorkingDirectory=/home/pi/myfare/
Type=simple
ExecStart=/home/pi/myfare/main

[Install]
WantedBy = multi-user.target

つまりこれを指定しないと、実行ファイルからファイルを見つけられなくなります。

systemdの記述方法はネットにたくさんありますが、ここに行き着くのに一時間以上。大本のマニュアル見た方が早かったかと思いますが、ともかくも以下の一連のコマンドの手順で自動起動できました。


サービスファイルを記述して、daemonのリロード
$ sudo systemctl daemon-reload
起動確認
$ sudo systemctl start myfare.service
正常に起動していることを確認
$ sudo systemctl status myfare.service
起動時にサービスを有効化
$ sudo systemctl enable myfare.service

 

admin

 

Golangアプリは単純にクロスビルドしても動かない(DBドライバがcgo使ってる)

Macで開発したアプリをラズパイで動かそうとしましたが、そのままでは動かない。なぜならgormもdbドライバーもcgoを使っている、つまりターゲットのgccを用意してそれを指定しないといけないから。

とりあえず動かすだけなら、すごく時間はかかりますがラズパイでビルド、2時間ぐらい放置してたらビルド完了してました。

実行ファイルを起動すると、Macよりは多少レスポンスは遅いのですがちゃんと動作しています。

<layout.html>

これだけはws://mbair.local:8080/wsをラズパイに変更が必要です。

 window.onload = function () {
  socket = new WebSocket("ws://mbair.local:8080/ws");
  socket.onopen = function () {
    append_message("system", "Socket Connected");
  };
  socket.onmessage = function (event) {
    append_message("server", event.data);
  };

  const send = function (){
    socket.send("")
  }
  setInterval(send, 500);

};

クロス環境をどうするかですが、Dockerがおすすめのようなので、それでやってみます。

メモリ使用状況は、こんな感じです、クライアント一台だけで、

すぐにもう一台増やすと、およそ200KBぐらい増えていますが、この程度では普通には十分です。

 

admin

M5StackC plusとラズパイ Model B+をつなぐ

いずれMyfareカードリーダーのアプリはラズパイで動かすので、つないでみた。まずはUSBがtype-Aで物理的に接続の簡単なラズパイ Model B+でつないでみます。最初I2Sのオーディオ拡張ボード挿入したままだと電源入らずラズパイも起動しないから、拡張ボードを外すと起動できました。

外部電源が不足か、ラズパイ本体から電力供給できないかのどちらかですが、USB type-Aにはmini-Bの5Vから直接供給しているので外部電源の容量不足のようです。

ラズパイからの見え方は、

$ lsusb
Bus 001 Device 005: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Device 005が該当で、当然ながらMacで読んだ値と同じで、

port.VID == “0403” && port.PID == “6001”

となります。

他の要因もあるから、Macでクロスコンパイルしたバイナリがそのまま動くかどうか?

 

admin

Raspberry PIのホスト名を変える

ラズパイがネットワーク内に複数台存在しているとmDNSで識別できないから、どれかの変更が必要です。

そのためには、

/etc/hostnameと/etc/hostsを二つとも変更します。

/etc/hostname
/etc/hosts

pi@raspberrypi:~ $ cat /etc/hostname
rasp-b

pi@raspberrypi:~ $ cat /etc/hosts
127.0.0.1	localhost
::1		localhost ip6-localhost ip6-loopback
ff02::1		ip6-allnodes
ff02::2		ip6-allrouters

127.0.1.1		rasp-b

変更後にリブートすれば、サイドのログオン時には設定したホスト名が使えるようになります。

 

admin

 

Goの並行処理

Goの並行処理は、関数の前にGoを入れるだけで処理対象になります。リソースの排他処理が必要なときには他の言語と同様なmutexを使う、あるいはチャネルを使っても良さそうです。

<素数を求めるコード>

package main

import (
    "sync"
	"time"
	"fmt"
)

func main() {
	var wg sync.WaitGroup
	var mu sync.Mutex
	c := 2
	odd := []int{}
	tStart := time.Now()
	for i := 2; i <= 10000*1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			flag := true		// if odd number, stay "true"
			mu.Lock()
			defer mu.Unlock()
			for j :=2; j*j <= c ; j++ {
				if c%j == 0{
					flag = false
					break
				}
			}
			if flag == true{
				odd = append(odd, c)
			}			
			c++
		}()
	}
	wg.Wait()
	tStop := time.Now()
	fmt.Println(len(odd))

	el := tStop.Sub(tStart)
	fmt.Println(el)
}

mutexを宣言する場所はこのケースではc++の直前でも良さそうなのですが、この位置じゃないとちゃんと結果が出ません。(TBD)

ちなみにC++との実行速度の比較、

千万までの素数計算をM1 Macでさせると、

C++ : およそ1.5秒、Go : およそ6秒と四倍程度遅い。スクリプト言語とは比べようもなく速いのですが。

しかしGoで並行処理をやめて(関数の先頭にgoを付けない)シングルスレッドにすると1.3秒程度で処理完了するから、並行処理にするとオーバーヘッドの分遅くなるだけです。実はGoはシングルスレッド(ネーティブで)でもマルチコアで並列処理を実行するようですね。

—————————————————

さらに以下のコードをM1 MacとRaspberry PI B+(700MHz single core)で実行速度を比較(実行速度向上の点からはほぼ無意味な並行処理)すると、

おおよそラズパイは1/150の実行速度、

 

 

admin

 

 

 

Goの標準のwebサーバー機能(@RaspberryPI)

Goの標準のライブラリにnet/httpというのがあって、これを使うとwebサーバーが簡単に立ち上げできます。もちろん複雑なことをやるならば他の言語と同じようにフレームワーク(実は単にnet/httpのラッパーらしい)が必要となるのですが。

ともかくも、以下のコードだけでhttp://raspberrypi.local:4000で実行ファイルディレクトリのpubディレクトリにあるindex.htmlの静的ページを返します。別にラズパイ以外でも同じなのですが、実際に使うのはラズパイだろうからラズパイでやっています。

package main

import (
    "net/http"
)

func main() {
    fs := http.FileServer(http.Dir("pub"))
    http.Handle("/", fs)
    http.ListenAndServe(":4000", nil)
}

ブラウザからアクセスするとこんな感じです、スタイルは未指定。

今時の言語ではwebサーバーは特別に分離しないで言語と一体化が自然な流れになってきています。

 

admin

 

Golangでクロスビルド

Go言語の特徴の一つだと思いますが、クロス環境のバイナリを環境変数の指定で作成できること。

例えば
Raspberry PIのような決して早くはないハード用のバイナリをMacで作成するのは現実的だろうと思う。

<hello_go.go>

package main
import "fmt"
func main(){
	fmt.Println("hello Go", 2*3)
}

のシンプルなソースを環境変数指定でビルドします。Raspberry PIの環境変数は、

$ go env
GO111MODULE=""
GOARCH="arm"
GOBIN=""
GOCACHE="/home/pi/.cache/go-build"
GOENV="/home/pi/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/pi/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/pi/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_arm"
GOVCS=""
GOVERSION="go1.19.4"
GCCGO="gccgo"
GOARM="6"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/pi/go_prj/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -marm -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1086455489=/tmp/go-build -gno-record-gcc-switches"

必要なのは、上のリストでGOOS=linux GOARCH=arm GOARM=6の部分、

ビルドを以下のコマンドで実施して、

$ GOOS=linux GOARCH=arm GOARM=6 go build hello_go.go

scpでRaspberry PIに転送でうまく実行できました、最初どこかのページでGOARM=7になっていたので、それで実行すると、

$ ./hello_go

Illegal instruction

と言われたので、ラズパイ環境からそのまま使用するのが間違いないでしょう。当然ラズパイでもバージョンで異なるはずですが、ここで使ったのはかなり初期の素のラズパイB+です。

ただし、ネーティブ環境よりはビルド時間遅くなっています。これはある意味当然かもしれませんが、それでもラズパイでビルドするよりはずっとマシだと思います。

 

admin