Goのフルスライス

あまりWebの記事中にはフルスライスという表現は出てこないようですが、スライスのスライス作成時は値参照ではなくてメモリは共有されるので、作成したスライスの振る舞いが意図したようにはなりません。

フルスライスとはスライスからスライス作成時にキャパシティと長さ(length)を同じにすることでappend()したときに必ず別領域に新たなスライス用のメモリを確保することで参照ポインタを分離して元のスライスとの干渉を避ける方法です。

y := x[:2:5]の5がキャパシタ指定です。

package main

import "fmt"

func main() {
	x := make([]int, 0, 5)
	x = append(x, 1, 2, 3, 4)
	fmt.Println("x:", x)
	y := x[:2:5]
	fmt.Println("y:", y)
	fmt.Println(len(x), len(y))
	fmt.Println(cap(x), cap(y))
	y = append(y, 30, 40, 50)
	x = append(x, 60)
	fmt.Println("x:", x)
	fmt.Println("y:", y)
	fmt.Println(len(x), len(y))
	fmt.Println(cap(x), cap(y))
}

実行結果は、メモリを共有しているのでスライス名は別でも相互干渉しています。

x: [1 2 3 4]
y: [1 2]
4 2
5 5
x: [1 2 30 40 60]
y: [1 2 30 40 60]
5 5
5 5

フルスライスにすると、

y := x[:2:2]

メモリが別領域に確保されるので干渉は発生しません。領域は1024バイト以下なら領域不足の都度二倍されるので本来は3バイトですが、6バイト確保されています。1024以上なら25%増加になるようですが、これは保証値ではなくて今後のバージョンアップで変更されるかもしれません。いずれにしてもスライスのサイズが大きくなるとメモリ領域確保とデータ転送が発生するので、サイズが大きければ性能に影響してきます。

x: [1 2 3 4]
y: [1 2]
4 2
5 2
x: [1 2 3 4 60]
y: [1 2 30 40 50]
5 5
5 6

スライスのキャパシティとサイズの遷移を図示すると以下の手書きのようになります。つまり

 

admin

Mifareカードリーダー

非接触カードで出席管理とか入室管理をするための素材です。日本はFericaですが世界的にはMifareが主流だし、Fericaカードより安いから用途によるけれどもセキュア性が重要でなければMifareで良いんじゃないかと思う。

<hardware>

いつものスイッチサイエンスで購入、送料安いしすぐ届くから便利だと思う。

https://www.switch-science.com/products/8301

<sample code>

https://github.com/m5stack/M5Stack/tree/master/examples/Unit/RFID_RC522

のコードをそのままM5Stackで動かしてみる。環境はVScode + PlatformIOです。

<ソースコードのコメントから>

The card reader and the tags communicate using a 13.56MHz electromagnetic field. The protocol is defined in ISO/IEC 14443-3 Identification cards. eg Mifare or NTAG203(古い規格)

まあMifareだけでFericaは読めないから、どのカードが何の規格採用というのは理解出来てないけれどもセキュアなカードはFerica使っているらしいから、以下の試験結果も納得できます。

キャッシュカード、運転免許証、マイナカード、ETCカード、PASMO、クレカ複数でやってみてUIDが認識されたのはクレカが2枚だけ。

唯一認識されたクレカの読み取り情報見ると7-BYTE UIDのようです。認識された2枚のUIDは当然別物になってます。従って一番単純にはUIDだけで識別ができます、それほどセキュアではないけれども。

 

受信部にはコイルが入っていて、電磁的な結合でエネルギー供給して読み取りや書き込みができます。Mifareカード買ってアプリ作ってみよう。

 

admin

 

 

recursive call

どの言語にも存在すると思いますがrecursive call(再帰呼び出し)、よく例として挙げられるのがnの階乗です。

recur()が再起呼び出しを使う方法でfact()は通常の繰り返し処理です。これだけ見たらなぜ再起処理必要なのというところでしょうが、例えばbinary tree(二分木)ではrecursive callが必須でこれ以外の方法ではうまく記述できないはずです。

package main

import (
	"fmt"
)

func main(){
	fmt.Println(recur(10))
	fmt.Println(fact(10))

}

// recursive call
func recur(n int) int{
	if n == 2{
		return 2
	}
	return n*recur(n -1)
}

// conventional method
func fact(n int) int{
	m := 1
	nf := n
	for i := 1; i <= n; i++{		
		m *= nf
		nf--
	}
	return m
}

以下はweb上に挙げられてるソースにノードの遷移がわかるようにPrint文を埋め込んだものです。よく見るとinsert()もprintNode()も再帰処理を使って同じような構造になっているのが理解できると思います。さらにdelete()を追加してもそうなります。

/*
https://selfnote.work/20210930/programming/golang-binary-tree-2/
*/

package main

import (
	"fmt"
)

type Tree struct {
	node *Node
}

type Node struct {
	value int
	left  *Node
	right *Node
}

func (t *Tree) insert(value int) *Tree {
	if t.node == nil {
		t.node = &Node{value: value}			// used only at the first node 
	} else {
		t.node.insert(value)
	}
	return t
}

func (n *Node) insert(value int) {
	if n.value >= value {
		if n.left == nil {
			n.left = &Node{value: value}
			fmt.Println("value_add_l", value)
		} else {
			fmt.Println("skip_l", value, n.left.value)
			n.left.insert(value)
		}
	} else {
		if n.right == nil {
			n.right = &Node{value: value}
			fmt.Println("value_add_r", value)
		} else {
			fmt.Println("skip_r", value, n.right.value)
			n.right.insert(value)
		}
	}
}

var i = 0

func printNode(n *Node) {
	if n == nil {
		i += 1
		fmt.Print("nil;")
		return
	}

	fmt.Println("value", n.value)
	printNode(n.left)
	fmt.Println("l",i, "node", n.value)
	printNode(n.right)
	fmt.Println("r",i, "node", n.value)
}

func main() {
	t := &Tree{}
	t.insert(2).
		insert(7).
		insert(5).
		insert(6).
		insert(1).
		insert(11).
		insert(9).
		insert(4).
		insert(20).
		insert(10)

	fmt.Println("-----------------------")
	printNode(t.node)
}

以下に実行結果から手書きでprintNode()の遷移を書いてみましたが、このように動作してます。

 

で、binary treeってどこで使われるの?ですがエンドのアプリで記述するよりもDBの内部処理などでは当たり前に使われているようです。insert(), delete(), search()ってDBでは普通に使われて、なおかつそれらを配列やスライスで表現させるとデータ追加の都度に再度作り直しが発生しますが、binary treeならば追加処理も簡単、さらにはアクセスも平均でn*log(n)で済むだろうから効率的であるとも言えます。

 

admin

Go言語(O’REILLYの初めてのGo言語)

以下の記事の書籍ですが、

https://isehara-3lv.sakura.ne.jp/blog/2023/01/07/go-lang/

とりあえず通読、理解の浅いところも無論ありますが。

O’Reilly本はハズレが少ないけど、この本も当たりでしょう。Goはかなりバージョンアップが頻繁ですが、1.18で追加されたgenericsもきちんとカバーさせてるし。「初めての」意味はもちろんコード書くのが初めてではなくて他の言語の経験が前提です。

副題にイディオマティックというタイトルがつけられていますが、これはGoFでは無いけれどもGoのミニデザインパターン的なことを言っているんだと思います。

結局Go言語の特徴(らしさ)は、並行処理はともかくも型定義、構造体とインターフェースに集約できるような気がします。

 

admin

 

Golang型指定でのチルダの意味

型指定時にチルダを使って~intとか~stringとか出てきますが、どういう意味かというと基底型を意味するのだと。

例えば以下のコードは、

https://github.com/mushahiroyuki/lgo/blob/main/example/ch15/ex1506b.go

を多少修正したものですが、独自の型をstringの拡張として定義するとstringに~なしではコンパイルエラーになりますが~を付けることでstringの派生型も含めることが出来るわけです。

package main

import (
	"fmt"
)

type BuiltInOrdered interface {
	~string | int | int8 | int16 | int32 | int64 | float32 | 
		float64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr

} 

func Min[T BuiltInOrdered](v1, v2 T) T { 
	if v1 < v2 {
		return v1
	}
	return v2
}

type Mystring string

func main() {
	var Astring Mystring = "a"
	var Bstring Mystring = "b"
	fmt.Println(Min(Astring, Bstring)) 
} 

 

admin

 

 

brewとGolang版数

Golangは2023/2に1.20になっていますが、brewで配布されるのは1.19.5が最新版になっています。

brewの場合には多少の遅延があるということだろうと思いますが、とりあえず実害はありませんが。

 

admin

 

M5Stackでバッテリ容量が読み取れない

パルスオキシセンサーでバッテリー容量の表示をしようと思ってコードを追加しましたが、センサーの初期化処理が干渉しているようで値(初期化処理後は-1が帰る)が読み取れません。

void setup()
{
  M5.begin();
  M5.Power.begin();
  Wire.begin();         // Wire init, adding the I2C bus.
  Serial.begin(115200); // to PC via USB
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextSize(4);
  // Initialize sensor
  if (!pox.begin()) {
    M5.Lcd.println("FAILED");
    for(;;);
   } else {
    M5.Lcd.println("SUCCESS");
  }
  int bat_level = M5.Power.getBatteryLevel();
  Serial.println(bat_level);

loop()処理で読み取れないのでsetup()処理中に読み出しを埋め込むと// Initialize sensorの直後の(

pox.begin()

)から後ろでは-1が帰るようです。

なんだろう?

 

admin

 

 

心拍センサーでパルスオキシメーターを作る

心拍センサーというのが市販されていて、それを使うとパルスオキシメーターが作れるので使ってみました。

https://qiita.com/ghibi/items/aa9ae016dbbe9138a505

にやりたいイメージ通りの例があるので、ほぼそのままを使っています。ただしVScodeで使おうとすると関数を先に定義しないと呼び出せないので、入れ替えはしてあります。関数宣言でもいいですが、これはArduino IDEとVScodeの差分です。

また、デフォルトの設定ではうまく測定値が返ってこないので、

  pox.setIRLedCurrent(MAX30100_LED_CURR_4_4MA);
  //Register a callback for the beat detection
  pox.setOnBeatDetectedCallback(onBeatDetected);
pox.setIRLedCurrent(MAX30100_LED_CURR_4_4MA); 行を追加して、IR ledの電流値を調整しています。画面左上のHeart Pulseマークを見ながら指を当てて測定するのですが、測定値の安定には数秒かかります。
P.S. (2023/2/9)
ライブラリのURL追記
admin

Golangのtesting

Go言語にはテスト環境も整備されていますが、ここではベンチマークの例です。testingモジュールのベンチマーク機能を使うことで、わざわざソースコードに性能測定のためのコードの埋め込みが不要になります。ここでは素数計算のコードをターゲットとしてみました。

package adder

import (
	"fmt"
	"runtime"
	"time"
)

func oddCalc() {
	runtime.GOMAXPROCS(8)
	c := 2
	odd := []int{}
	tStart := time.Now()
	for i := 2; i <= 10000*1000; i++ {
		func() {
			flag := true // if odd number, stay "true"
			for j := 2; j*j <= c; j++ {
				if c%j == 0 {
					flag = false
					break
				}
			}
			if flag {
				odd = append(odd, c)
			}
			c++
		}()
	}
	tStop := time.Now()
	fmt.Println(len(odd))

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

同じディレクトリにベンチマーク用の以下のソースコードファイルを用意しておきます。ターゲットと同じファイル中でも構わないと思いますが、ベンチマークを簡単に実行の趣旨からすると外れてます。

package adder

import (
	"testing"
)

func BenchmarkOddCalc(b *testing.B) {
        oddCalc()
	} 

実行結果は以下のとおりです。終了コードの判定もエラーコードも返していないのでpass~~が出力されています。

% go test -bench=. -v
goos: darwin
goarch: arm64
pkg: test
BenchmarkOddCal
664579
1.241648416s
BenchmarkOddCal-8              1        1241672042 ns/op
PASS
ok      test    1.485s

ソースコードに埋め込んだ時間測定とは微妙に値が異なりますが、精度から見れば誤差範囲です。

と言うわけでGoではわざわざ性能測定のためにターゲットのコード中にコードの埋め込みは要らないよという話でした。

 

admin

UTMでUbuntuデスクトップArm版をM1 Macbookに、

昨年夏はイマイチ動きがおかしくてデスクトップのインストールもできなかったのですが、そろそろと機は熟したようなのでインストールしてみた。

この前にVMwareでのインストールも試みたが、BIOSのメニューから進まないから現時点では諦め。

https://dev.to/andrewbaisden/how-to-install-ubuntu-linux-on-apple-silicon-macbooks-1nia

このリンクの通りにやってみただけですが、ちゃんとデスクトップ画面は立ち上がりました。なぜかChromeは依存関係解決できないと言われて、インストールできないのですが。

P.S. (2023/2/3)

VScodeもインストールできず、いまいちな完成度、UTMのせいなのかArm版Ubuntuだからなのか。

P.S. (2023/2/10)

Ubuntuのアップデート後に再度トライしてみたらVScodeインストールできた、ブラウザはChromium、どこまでChromeと互換あるのか?

 

admin