PostgreSQL上でのベンチマークをとる準備
DB全般をあまり知らないので、仕事上使っているPostgreSQL上で簡単なベンチマークをとる。
実行環境
- ASUS ROG Zephyrus GX502
- Ubuntu 20.04
- PostgreSQL 12.5
- SBCL 2.0.11
- Quicklisp 2020-12-20
- ASUS ROG Zephyrus GX502
参考文献
PostgresSQLの準備
https://www.postgresql.jp/document/12/html/tutorial-start.html ローカルにPostgresSQLをインストールする。
psqlからデータベースを作る。
createdb test
練習用ロールを作る。
CREATE USER "myuser" CREATEDB PASSWORD '1234';
cl-dbiでのアクセス
cl-dbiで対象dbに接続する。
(ql:quickload :cl-dbi) (defvar *connection* (dbi:connect :postgres :database-name "test" :username "myuser" :password "1234"))
実行用の関数を定義する。
(defun run (string &key params silent) (let ((query (dbi:execute (dbi:prepare *connection* string) params))) (unless silent (loop for row = (dbi:fetch query) while row do (pprint row)))))
テーブルを作る。
(time (run "CREATE TABLE bench1 (uuid varchar(36), int_data int, real_data real);"))
データを登録する。
(run "INSERT INTO bench1 VALUES (?, ?, ?);" :params (list (print-object (uuid:make-v4-uuid) nil) (random 100) (random 100.0)) :silent t)
1つデータを見る。
(run "SELECT * FROM bench1 LIMIT 1;" ) ;; (:|uuid| "9D9624B4-9275-4F67-8754-B8F27710D77C" :|int_data| 31 :|real_data| 31.218767)
データ数を見る。
(run "SELECT COUNT(*) FROM bench1;" ) ;; (:|count| 1000)
ベンチマーク用データ追加
ちょっとした問題として、SELECTのベンチマークをとるためにデータのINSERTを100万回くらい行おうとすると途中でheap exhaustedとなる。 どこかでメモリーリークがあるみたいだが、よく分からないがとりあえずヒープ最大を20GBくらいに増やす。
ros config set dynamic-space-size 20000
10万回は通ったのでこれで何度か実行する。
(time (dotimes (_ 100000) (run "INSERT INTO bench1 VALUES (?, ?, ?);" :params (list (print-object (uuid:make-v4-uuid) nil) (random 100) (random 100.0)) :silent t)))
10万回ループのデータ登録を10回実行する。 10万回あたりだいたい145秒かかった。
SxQLの導入
SQLを文字列で埋め込んでいくのも面倒なのでSxQLを導入する。
(ql:quickload :sxql) (defun run (sxql &key silent) (multiple-value-bind (sql binds) (sxql:yield sxql) (pprint sql) (let ((query (dbi:execute (dbi:prepare *connection* sql) binds))) (unless silent (loop for row = (dbi:fetch query) while row do (pprint row))))))
レコードの全数カウント。
(time (run (sxql:select ((:count :*)) (sxql:from :bench1))))
Android上のRustでSQLiteを扱おうとしたときに困った事
Android上のRustからSQLiteを扱おうとしたときに、ライブラリの都合上rusqliteを使用した。 で、Connection::open_in_memory()は動いたのだが、ファイルに保存しようとしてConnection::open("dummy")のような事をしたらエラーにすらならずopenが返ってこなくなった。
色々いじってみたところ、結局dart側の getApplicationSupportDirectory() でディレクトリの絶対パスを取得し、それをくっ付けたパスにしたところ動作した。 絶対パスを指定しないで変な場所にアクセスしようとしたのはまあこちらが悪いのだが、せめて権限エラーか何かになって欲しかった。。。
Claspをビルドする2020-05
Common Lisp処理系のClaspは、依存ライブラリやリビジョンが色々変わって中々出来ない事が多いので、現時点でビルド可能な組み合わせについて。
ホスト計算機はUbuntu 18.04.4 x86_64 で確認を行っている。 Ubuntuのパッケージ方針がよく分からないが、18.04でも時期によってLLVMのバージョンが色々あったりするので、例えばapt install clangとやると、Claspをビルドできないclangが入る可能性がある。 改めてUbuntu 18.04のLLVM関連パッケージをみると、現在では3.9から9までそろっているのでUbuntu 18.04だけを考えればexternals claspでLLVMをビルドする必要は無いかもしれない。
testingブランチ
ブランチの扱いが時々変わるのでよく分からないところがあるが、一応安定版に相当すると思われるブランチ。
現在の最新バージョンは2019-09-29の f0cb8960867be6ed58a82066abb0da2df9da72b3
でこれはgithub上 0.4.3 のタグがついている最新安定版と思われる。
LLVM 6を必要としており、現状のroswellからインストールできるのはこのブランチを追従してることが多い。
roswellからのインストール
完全な手順を目指して、dockerのubuntu:bionicでインストールを行う。
docker pull ubuntu:bionic docker run -it --name roswell-clasp ubuntu:bionic
dockerのubuntuの中でroswellインストール。
apt update apt -y install git build-essential automake libcurl4-openssl-dev git clone -b release https://github.com/roswell/roswell.git cd roswell git checkout v20.04.14.105 sh bootstrap ./configure --prefix=$HOME/.local make make install export PATH=$HOME/.local/bin:$PATH ros setup ros run -- --version
最終的にroswellが動いて、インストールされたSBCLは2.0.4だった。
SBCL 2.0.4
Claspをインストール。まずは依存ライブラリのビルド。
apt -y install cmake python binutils-dev time ros install externals-clasp+/6.0.1
数十分待つとビルドが終わる。エラーになるようであれば、~/.roswell/lib/x86-64/linux/externals_clasp/6.0.1
の中で make
を実行してエラーに対処する。(環境合わせてこの手順通りならエラーにならないはずだが)
apt -y install libgmp-dev libgc-dev zlib1g-dev libelf-dev libncurses-dev libbsd-dev libboost-filesystem-dev libboost-date-time-dev libboost-program-options-dev libboost-iostreams-dev time ros install clasp
数十分待つとビルドが終わる。エラーになるようであれば、 ~/.roswell/src/clasp/2019-09-29
の中で ./waf configure build_dboehm
を実行してエラーに対処する。(環境合わせてこの手順通りならエラーにならないはずだが)
ros run -- --version
clasp-boehm-0.4.2-1534-gf0cb89608
普通にビルド
公式ビルド方法でもそのままビルドできる。 Build Instructions · clasp-developers/clasp Wiki · GitHub
docker run -it --name clasp-testing ubuntu:bionic
apt update apt install -y gcc g++ llvm clang-6.0 libclang-6.0-dev cmake libgc-dev libgmp-dev binutils-gold binutils-dev zlib1g-dev libncurses-dev libboost-filesystem-dev libboost-regex-dev libboost-date-time-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libunwind-dev liblzma-dev libelf1 libelf-dev libbsd-dev sbcl git git clone https://github.com/clasp-developers/clasp.git cd clasp git checkout f0cb8960867be6ed58a82066abb0da2df9da72b3 time ./waf configure build_dboehm
数十分待つとビルドが終わる。
build/clasp
実行するとREPLが起動する。
Starting cclasp-boehm-0.4.2-1534-gf0cb89608-cst ... loading image... Top level in: #<PROCESS TOP-LEVEL @0x7900ea9>. COMMON-LISP-USER>
devブランチ
devブランチは最新開発版で現在の dee626ca92ce5b2154c1903b17d26444a7f69de6
はLLVM 9を必要としている。
docker run -it --name clasp-dev ubuntu:bionic
apt update apt install -y git sbcl clang-9 libclang-9-dev libgmp-dev libgc-dev zlib1g-dev libelf-dev libncurses-dev libbsd-dev libboost-filesystem-dev libboost-date-time-dev libboost-program-options-dev libboost-iostreams-dev git clone https://github.com/clasp-developers/clasp.git cd clasp git checkout dee626ca92ce5b2154c1903b17d26444a7f69de6 echo BOEHM_GC_ENUMERATE_REACHABLE_OBJECTS_INNER_AVAILABLE=False > wscript.config time ./waf configure build_dboehm
数十分待つとビルドが終わる。
build/clasp
実行するとREPLが起動する。
Starting cclasp-boehm-0.4.2-2436-gdee626ca9-cst ... loading image... Top level in: #<PROCESS TOP-LEVEL @0x9adbb69 (Running)>. COMMON-LISP-USER>
その他
Android版のFlutterからRustを呼び出す
Android版のFlutterからRustの関数を呼び出す最低限のメモ。 それらしい記事はいくつかあるものの、理解が出来てないのかバージョンの違いからか中々上手く行かなかったので。 Fluttter, Android, Rust等々の作法として正しいかどうかは正直分からないし、少し前まではDartから直接FFIが扱えなかったのでJNI経由だったようだ。
以下参考にしたページ。
- Rust once and share it with Android, iOS and Flutter | Roberto Huertas
- Using FFI on Flutter Plugins to run native Rust code
- Binding to native code using dart:ffi - Flutter
- Flutterプラグインでdart:ffiを使ってみる - Speaker Deck
環境
ツールバージョン
インストール
Flutter
普通にインストール。
Rust
rustupをインストールしrustのandroid関連パッケージをインストールする。
curl https://sh.rustup.rs -sSf | sh rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android cargo install cargo-ndk
新規Flutterプロジェクト作成
- Start a New Flutter project選択
- Flutter Application選択
- New Flutter Application
- Project name :
flutter_dart_ffi_rust
- Flutter SDK path :
/home/username/.local/flutter
- Project location :
/home/username/AndroidStudioProjects/flutter_dart_ffi_rust
- Description :
Flutter Dart FFI Rust
- Package name :
com.example.flutterdartffirust
- AndroidX :
on
- Platform channel language
- Project name :
新規Rustライブラリ作成
Flutterプロジェクトの中ににRustのライブラリを作成する。
cd flutter_dart_ffi_rust && mkdir rust && cd rust cargo init --name rust_sample --lib
src/lib.rsを書き換えてC ABIの関数echoを作る。
use std::ffi::{CStr, CString}; use std::os::raw::c_char; #[no_mangle] pub unsafe extern "C" fn echo(to: *const c_char) -> *mut c_char { let c_str = CStr::from_ptr(to); let s = match c_str.to_str() { Ok(s) => s, Err(_) => "Err", }; CString::new(format!("From Rust {}", s)).unwrap().into_raw() }
rust/build.sh
ビルドスクリプト。
ローカルのシミュレータやAndroid実機等で動かすため複数のプロセッサ向けにコンパイルする。
export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/21.0.6113669 for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android do cargo ndk --target ${target} --android-platform 22 -- build --release done
rust/install.sh
インストールスクリプト。
最終的な実行時に、共有ライブラリが参照できる場所は具体的にどこなのか作法としてよく分からないが、JNIと同様Flutterの android/app/src/main/jniLibs/
以下に置いて動いた。
INSTALL_PATH=../android/app/src/main/jniLibs INSTALL_FILE=librust_sample.so mkdir -p ${INSTALL_PATH}/arm64-v8a mkdir -p ${INSTALL_PATH}/armeabi-v7a mkdir -p ${INSTALL_PATH}/x86 mkdir -p ${INSTALL_PATH}/x86_64 cp target/aarch64-linux-android/release/${INSTALL_FILE} ${INSTALL_PATH}/arm64-v8a/ cp target/armv7-linux-androideabi/release/${INSTALL_FILE} ${INSTALL_PATH}/armeabi-v7a/ cp target/i686-linux-android/release/${INSTALL_FILE} ${INSTALL_PATH}/x86/ cp target/x86_64-linux-android/release/${INSTALL_FILE} ${INSTALL_PATH}/x86_64/
Flutter側修正
pubspec.yaml
の dependencies:
にffiを追加する。
ffi: ^0.1.3
lib/main.dart
からRustのecho関数を呼び出す。
ffiのインポートを追加し、dll読み込み、呼び出す関数の型定義、echo関数の生成(?)を書く。
import 'dart:ffi'; import 'package:flutter/material.dart'; import 'package:ffi/ffi.dart'; final dll = DynamicLibrary.open('librust_sample.so'); typedef Echo = Pointer<Utf8> Function(Pointer<Utf8> to); final echo = dll.lookup<NativeFunction<Echo>>('echo').asFunction<Echo>(); void main() => runApp(MyApp()); // 略
lib/main.dart
の 'Flutter Demo Home Page'
を関数呼び出しで置き換える。
// 略 home: MyHomePage(title: Utf8.fromUtf8(echo(Utf8.toUtf8('dummy')))), // 略
実行
Zenfone6上で実行出来た。
ユニットテストでダミーの関数に差し替える
Common Lispでユニットテストを行う場合に、ユニットテストから呼び出した関数の内部で呼ばれる関数を本来の動作とは違う別の関数に差し替えたい場合がある。
関数を一時的に置き換える方法についてのメモ。
関数
例えば次の例では関数 charlie
をテストしようと思った場合に、その中で呼び出される関数 alfa
を差し替えたいとする。
(defun alfa () :bravo) (defun charlie () (alfa)) (pprint (eq (charlie) :delta))
上記のコードは、charlie
が :bravo
を返すため nil
となる。
危険なバージョン
最低限の機能を示すための危険なバージョンが次の通り。
(defun alfa () :bravo) (defun charlie () (alfa)) (setf (symbol-function 'alfa) (lambda () :delta)) (pprint (eq (charlie) :delta))
上記のコードは、charlie
が :delta
を返すため t
となる。
最後から2行目で symbol-function
への setf
で、関数 alfa
や charlie
自体の変更をすることなく呼び出される関数 alfa
を変更する事が出来ている。
多分安全なバージョン
ユニットテストで一時的に関数を変更したいということを考えると、最後は元々の関数に戻って欲しい。そのバージョンが次の通り。
(defun alfa () :bravo) (defun charlie () (alfa)) (let ((orig-func (symbol-function 'alfa))) (unwind-protect (progn (setf (symbol-function 'alfa) (lambda () :delta)) (pprint (eq (charlie) :delta))) (setf (symbol-function 'alfa) orig-func))) (pprint (eq (charlie) :delta))
最後から3行目のコードは関数 alfa
が差し変わっているので t
となり、最後の行は関数 alfa
が元に戻っているので nil
となる。
unwind-protect
は内部でなんらかコンディションが発生した場合でも確実に元に戻せるように。
メソッド
(defclass delta () ()) (defmethod echo :before ((delta delta)) (pprint :foxtrot)) (defmethod echo ((delta delta)) (pprint :golf)) (echo (make-instance 'delta))
実行結果
:FOXTROT :GOLF
クラスのメソッドを差し替える場合には、総称関数およびオブジェクトシステムに関連して一手間かける必要がある。 まず総称関数オブジェクトを取得し、それを使ってオブジェクトシステムからqualifierやspecifierの一致するメソッドを取得する。
一度beforeメソッドを削除して、元に戻す。
(defclass delta () ()) (defmethod echo :before ((d delta)) (pprint :foxtrot)) (defmethod echo ((d delta)) (pprint :golf)) (let ((d (make-instance 'delta)) (orig-method (find-method #'echo '(:before) (mapcar #'find-class '(delta))))) (remove-method #'echo orig-method) (echo d) (add-method #'echo orig-method) (echo d))
実行結果
:GOLF :FOXTROT :GOLF