Rustの基本的なsyntax

初めてRustをPlaygroungで動かしてみたけど、結構独特だと思う

https://www.rust-lang.org

“for” loopはイテレータ専用

“The for in construct can be used to iterate through an Iterator. “

通常のループ処理は無限ループ使ってcontinuebreakで条件判断

<初めてのRustのコード>

fn main() {
    let mut i = 0;
    loop {
        println!("i = {}", i);
        if i == 10{
            break i;
        }
        i += 1;
    };
}

・mutable変数はmutで宣言が必要、宣言しないとimmutable変数になる

・println!では必ずプレースホルダー”{}”を入れる

loop}にも;が必要、if{}}の後ろにはあっても無くてもコンパイルエラーにはならないけれども

・ifの条件をカッコ()で括らないのはGoと同じ

 

admin

Goは値渡し言語、

タイトルの通りですがGo言語は値渡し、例えば関数に引き渡す引数は値が引数のアドレスとは違う場所の関数内で使用する変数のアドレスにコピーされて使われます。

これでは都合悪い時もあるだろうから、参照渡しのためにポインターが存在知ると考えれば良いかもしれない。ポインターはC/C++の記法そのままで、

package main

import "fmt"

func main() {
	val := 20
	pointer := &val
	*pointer = 23
	fmt.Println("address : ", pointer, "value : ", val)
}


address :  0x14000126008 value :  23

のように使います、しかしGoでポインターを普段使いしたら、変数がmutableになることでコードの見通しが悪くなるから使うのは限定的だろうと思う。Goでは通常の変数はすべてimmutable扱いだから、逆に言えばポインター宣言は明示的なmutable宣言であるということです。

例にあるようなケースでわざわざポインターを使う必要性はないし、こんな使い方はしちゃいけない例です。数少ない例外はインターフェースを受け取るだけだとは『初めてのGo言語』の記載。

 

admin

 

 

 

M5Stackでの表示ちらつきの防止方法、

普通はM5Stackで表示内容の書き換えするときに、一度表示内容を消去する必要がありますが、それが表示がちらつく原因です。

ちらつき防止のためには、画面表示内容をフルで別のバッファーに用意しておいて、それを押し込めば表示消去時間が存在しないことになるのでちらつきの防止ができます。

以下のサンプルから、

https://craft-gogo.com/m5stack-sprite/

をそのまま実装してみましたが、確かにちらつきは見えなくなります。

画面書き換えの処理時間を見るために、前後で時間を取得して処理時間を見てみましたが、画面の押し込み(pushSprite())の処理時間は実は普通に画面を書き込む時間と変わらず、大体40msぐらい掛かっています。要は表示領域を一度クリアしないからちらつきを感じることなくなっているわけで、決してpushSprite()にして画面描画時間が高速化される訳ではありません。

 

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の並行処理と並列処理、

ダラダラと継続中ですが、とりあえずの現状のまとめです。

<条件>

M1 MacBook air

1000万までの素数計算してスライスに格納、昇順は考慮してない

 

<言えること>

・goはシングルスレッドでもマルチコアを使うように(並列処理するように)動く、これは便利だ

・channelのオーバーヘッドはmutexよりも大きい予想通りですが、ただしmutexのように個別の変数に対する考慮は不要だからエラーの入りにくいのがchannel

・コア数を強制的にしてしてやるとこのコードの場合には4コアぐらいが最適、もちろん状況により変わりますが

それぞれのソースコードはこちら、

https://github.com/chateight/go/tree/master/concpara

 

 

admin

Goの並行処理でchannelを使う

https://isehara-3lv.sakura.ne.jp/blog/2023/01/12/goの並行処理/

ではWaitGroup使って終了を判定しましたが、Go独自の機能にスレッド間での通信機能channelを使う方法もあります。どちらが良いというのではなくて、目的によって使い分けするんだと思いますが。

以下はリンクの処理と同じく一千万までの素数を求める処理です。40行目付近から下のループ処理でchannelを使って素数をスライスに格納しています。channelが存在しないことはタイマーの一秒待ちで判定しています。

package main

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

//
// to use channel instead of "WaitGroup"
//
// in this case, slower than WaitGroup. Channel may be useful when it takes long processing time and less inforamtion size
//
func main() {
	maxNumber := 1000*10000
	var mu sync.Mutex
	ch := make(chan int)
	defer close(ch)
	c := 2
	oddCh := []int{}
	tStart := time.Now()
	tStop := time.Now()
	for i := 2; i <= maxNumber; i++ {
		go func() {
			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{
				ch <- c
			}			
			c++
		}()
	}
	for i:= 0; i < maxNumber; i++{				// set odd numbers to the slice
		setBreak := false
		select {
		case p := <- ch:
			oddCh = append(oddCh, p)
			tStop = time.Now()
		case <- time.After(time.Second):		// to check last "ch" data
			fmt.Println("Time Out!")
			setBreak = true
		}
		if setBreak == true{
			break
		}
	}
	
	fmt.Println("len : ", len(oddCh))

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

実はWaitGroupを使った場合に比較して、処理時間は5倍ぐらいになっています。おそらく渡しているのが素数というint形式データを渡しているだけでオーバーヘッドが大きい。したがってchannelを使うのに細かなデータをやり取りするには向かないんだろうと思います。

いずれにしろこのような簡単な処理では、goroutineも使わずにgoの実行環境でマルチコアを使う方が一番高速なのだから、余計なことをやらないのがいちばんのようです。

 

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

 

 

3Dプリンタのノズル交換

購入二年半(Flashforge Adventure3)で寿命が来たようで、添付の針金で突いてもフィラメントが出てこない。酸化物が蓄積して最終的にこうなるようです。

で、消耗品だから新規に購入しましたが、昨年末から年末年始休みで入手はごく最近。

新旧比較すると、現行品は耐熱温度が240 -> 265℃になっています。その他微妙に色々なところも違いますが。

新しいノズルとカチッと音がして、ロック(両側の黒いポッチ)が戻るまで押し込みます。

フィラメント供給のパイプを接続、10mmのフレアナットレンチを使用。

ノズルの温度補正が必要らしい、手書きだから一品料理のように見えます。

ノズルの高さ調整、A4の紙が軽く接触する程度に調整しています。

ノズルからで始めのフィラメントは『赤い』、ノズルの中で何らかの保護剤が入っているようです。

テストプリントは元々内在されている20mm直方体で、まだ時々脱調はしているようなので、フィラメントの吸湿があるのか。そろそろ温度印加して脱湿するアクセサリ必要のように思います。

admin