Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions .cursor/rules/branch/fix.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,51 @@ fixブランチルール
===

このブランチで実装することは以下の通りです。
- `ducere new` 実行時に、生成される新規プロジェクトが `examples/aaa` 配下と同じディレクトリ構造・ファイル構成になるように `src/basolato/cli/functions/newImpl.nim` を更新する
- 新規プロジェクト生成時のテンプレート取得を Git リポジトリの clone / download に依存しない方針へ置き換える
- 生成する `.nimble` ファイル名を CLI で渡された `packageDir` と同名にする
- `ducere build` が生成する Nim ビルドコマンドの最適化方針を見直す。
- 候補となる Nim ビルドオプションを実際にコンパイルし、サンプルコードを使ってビルド時間・実行速度・生成物サイズを計測する。
- 計測結果を根拠に、`/application/src/basolato/cli/functions/buildImpl.nim` の本番ビルドオプションを更新する。
- 調査で得た知見が既存ドキュメントとずれる場合は、関連ドキュメントの補足や更新要否も整理する。

## 進捗
- [x] 現状調査: `examples/aaa` のディレクトリ構造と `src/basolato/cli/functions/newImpl.nim` の現在実装を確認した
- [x] `examples/aaa` から新規生成時に必要な最小ファイル群と、そのまま複製してよいファイル群を整理する
- [x] Git clone を廃止した場合のテンプレート同梱方法を決め、`newImpl.nim` の責務分割を設計する
- [x] `packageDir` を `.nimble` のファイル名と内容へ一貫して反映する実装方針を確定する
- [x] `newImpl.nim` を更新し、`examples/aaa` 相当の生成結果になることを確認する
- [x] `examples/aaa` の走査依存を除去し、ディレクトリとファイル内容を定数化したテンプレート生成へ切り替えた
- [x] 現在の `fix` ブランチ名とブランチルールファイルの対応を確認した。
- [x] 現行の `/application/src/basolato/cli/functions/buildImpl.nim` における本番ビルドオプションを確認した。
- [x] 計測対象とする Nim ビルドオプションの比較軸を確定する。
- [x] ベンチマークに使うサンプルコードと計測手順を用意する(`examples/example`、`scripts/bench_ducere_build_opts.sh`)。
- [x] 各ビルドオプションで複数回計測し、実行速度・ビルド時間・生成物サイズを比較する。
- [x] 採用方針を決め、`/application/src/basolato/cli/functions/buildImpl.nim` に反映する。
- [x] `ducere` ドキュメント(ja/en)に本番フラグの補足を追記した。

## 参考資料
- `examples/aaa`
- `src/basolato/cli/functions/newImpl.nim`
- `.cursor/rules/project.mdc`
- `.cursor/rules/branch.mdc`
- `/application/src/basolato/cli/functions/buildImpl.nim`
- `/application/src/basolato/cli/functions/serveImpl.nim`
- `/application/documents/ja/ducere.md`
- `/application/documents/en/ducere.md`
- `/application/runTest.sh`
- `/application/examples/benchmark/FrameworkBenchmarks/basolato/config/database.nim`
- `/application/examples/benchmark/web-frameworks/basolato/config.yaml`
- `/application/scripts/bench_ducere_build_opts.sh`

## 調査結果・設計まとめ
- 現在の `src/basolato/cli/functions/newImpl.nim` は `https://github.com/itsumura-h/nim-basolato-templates` を `git clone` し、`tmp/0.16` 配下の一部ディレクトリだけを移動する実装になっている
- この実装では `examples/aaa` に存在する `.env.example`、`app/README.md`、各種 `README.md`、`database/schema.nim`、`database/seeders`、`tests/test_sample.nim` などの初期ファイル群と完全には一致しない
- ユーザー要件として、今後は外部 Git リポジトリからテンプレートを取得せず、リポジトリ内に存在する雛形を元に新規プロジェクトを生成する必要がある
- 生成対象の正は `examples/aaa` とし、`aaa.nimble` のようにサンプル固有名を含むファイルは `packageDir.nimble` に置き換える前提で扱う
- 実装時は、サンプル固有値が残るファイルの有無を洗い出し、単純コピーで済むファイルと `packageDir` などの値を差し替えるファイルを分離して設計する
- `config.nims`、`.env`、`.env.example` はテンプレート複製ではなく `makeConfig(baseDir)` で生成し、それ以外のファイルは `examples/aaa` からローカル複製する方針にした
- `templateRoot` はカレントディレクトリ基準ではなく `currentSourcePath()` から上位へ探索して解決し、nimble install 後もパッケージ内の雛形を参照できるようにした
- 現在の最終方針は、`examples/aaa` を実行時に読むのではなく、`newImpl.nim` 内にディレクトリ一覧とファイル内容の文字列定数を持ち、CLI がディレクトリを作成してから各テンプレートを書き出す方式にすること
- 現状の本番ビルドでは `--threads:off`、`-d:ssl`、`-d:danger`、`-d:release`、`--parallelBuild:0`、`--passC:\"-flto\"`、`--passL:\"-flto\"`、`--panics:on` を付与している(2026-03 更新: `--stackTrace` / `--lineTrace` は計測の結果、本番から外す)。
- `optimize=speed` は `--mm:markAndSweep -d:useRealtimeGC`、`optimize=memory` は `--mm:orc -d:useMalloc` を選ぶ実装のまま(CLI 互換維持)。
- **コンパイル+バイナリ(`examples/example`、`nim c -f` を3回の wall 時間平均、Nim 2.2.8 / Linux amd64)**

| variant | avg compile (s) | bytes |
|---------|-----------------|-------|
| mem_danger_lto | 49.19 | 2380200 |
| speed_danger_lto | 48.43 | 2080824 |
| mem_danger_no_lto | 23.48 | 4848968 |
| speed_danger_no_lto | 17.03 | 2492344 |
| mem_nodanger_lto | 54.62 | 2654936 |
| speed_nodanger_lto | 51.74 | 2245248 |

- **`-flto`**: リンクは遅延するが、特に ORC ではバイナリが極端に肥大化しやすいため本番では継続採用。
- **`-d:danger`**: 同一 GC 設定内でコンパイル時間・サイズが有利な傾向。本番では継続採用。
- **`--threads:on`**: 標準サーバ(`server.nim`)は `Async` の GC-safety エラーでビルド不能。`--threads:off` を維持。
- **実行速度(`wrk -t2 -c20`、`/sample/fib/12`、markAndSweep 本番相当ビルド)**: `--stackTrace`/`--lineTrace` ありで約 1500 req/s、なしで約 2280 req/s(同一マシン・短時間計測)。本番からトレース用スイッチを外す判断とした。
- **memory vs speed GC**: 同一エンドポイントでは markAndSweep+useRealtimeGC が orc+useMalloc より桁違いに高スループット(計測時は数百 vs 千台 req/s オーダー)。CPU 負荷が高い処理では `optimize=speed` を推奨。メモリ制約環境では `memory` の意味は維持。
- 採用判断は「ビルドが通ること」を前提に、次の優先順位で行う。
- 実行速度
- 安定性と互換性
- 生成バイナリサイズ
- 実装更新時は、CLI 引数 `optimize=speed|memory` の既存インターフェースを壊さない方針を第一候補とする。互換性を壊す場合は、理由をブランチルールに追記してから着手する。
12 changes: 1 addition & 11 deletions documents/en/ducere.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ Options:
--httpbeast bool false set httpbeast
--httpx bool false set httpx
-a, --autoRestart bool false set autoRestart
-o=, --optimize= string "memory" memory|speed
```
By default, it will be compiled to run port 8000 with single-thread and multi-process settings.
When you build application, shell script file named `startServer.sh` is generated. Run this file to start server.
Expand Down Expand Up @@ -163,16 +162,7 @@ ducere build --httpbeast
ducere build --httpx
```

You can choose optimize option for memory or speed.
If memory is selected, `ORC` is used for less memory consumption. If speed is selected, markAndSweep is used for more throughput.

```sh
ducere build --optimize=memory
> nim c --mm:orc -d:useMalloc ... main

ducere build --optimize=speed
> nim c --mm:markAndSweep -d:useRealtimeGC ... main
```
Production builds pass `-d:danger`, `-d:release`, `-d:ssl`, link-time optimization (`-flto`), `--mm:orc`, and `--threads:off`. The memory management strategy is unified to `ORC`, providing a well-balanced configuration for memory efficiency and stability. The default asynchttpserver-based stack does not compile with `--threads:on` today (GC-safety), so multi-threaded compilation is not supported. `--stackTrace` / `--lineTrace` are omitted to favor runtime throughput and smaller binaries; invoke `nim c` yourself and add trace switches if you need rich production stack traces. `-flto` slows linking but usually shrinks the binary (without LTO, an ORC build of `examples/example` grew to several megabytes in our measurements).

### database scripts
Use these scripts when you want to apply migrations and seeders together.
Expand Down
12 changes: 1 addition & 11 deletions documents/ja/ducere.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ Options:
--httpbeast bool false set httpbeast
--httpx bool false set httpx
-a, --autoRestart bool false set autoRestart
-o=, --optimize= string "memory" memory|speed
```
何もオプションを付けない場合、8000番ポートを使い、シングルスレッド・マルチプロセスで起動します。
ビルドすると`startServer.sh`というシェルスクリプトが作られるので、これを実行することでサーバーを起動します。
Expand Down Expand Up @@ -159,16 +158,7 @@ ducere build --httpbeast
ducere build --httpx
```

optimizeオプションにはmemoryかspeedを選択することができます。
もしmemoryを選んだ時には `ORC` が使われ、メモリ消費が少なくなります。speedを選んだ時には `markAndSweep` が使われ、スループットがより多くなるようになります。

```sh
ducere build --optimize=memory
> nim c --mm:orc -d:useMalloc ... main

ducere build --optimize=speed
> nim c --mm:markAndSweep -d:useRealtimeGC ... main
```
本番ビルドでは `-d:danger`・`-d:release`・`-d:ssl`・リンク時最適化(`-flto`)、`--mm:orc`、`--threads:off` を付与します。メモリ管理方式は `ORC`に統一されており、メモリ効率と安定性のバランスが取れた設定となっています。標準の asynchttpserver 系スタックは現状 `--threads:on` ではコンパイルできないため、スレッド有効化は想定していません。スタックトレース用の `--stackTrace` / `--lineTrace` は実行速度とバイナリサイズ優先のため付けません。本番で詳細なスタックが必要な場合は `nim c` を直接実行し、必要なトレース用スイッチを追加してください。`-flto` はリンクに時間がかかりますが、同一ソースではバイナリサイズの削減に効きます(`examples/example` での比較では LTO なし ORC ビルドが数 MB 規模で肥大化)。

### database scripts
マイグレーションと seeder をまとめて実行したいときは、`database/` 配下のシェルスクリプトを使います。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ WORKDIR /basolato

# Install dependencies only; avoid building this benchmark package via nimble.
RUN nimble install -y -d
RUN ducere build -a --httpbeast
RUN ducere build -o:speed -a --httpbeast


FROM ubuntu:24.04 AS runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ license = "MIT"

# Dependencies
requires "nim >= 2.0.0"
requires "https://github.com/itsumura-h/nim-basolato#main"
requires "https://github.com/itsumura-h/nim-basolato#fix"

task test, "run testament":
echo staticExec("testament p \"./tests/test_*.nim\"")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import basolato/settings
import db_connector/db_postgres
import allographer/connection

when defined(release):
import std/cpuinfo

let maxConnections =
when defined(release):
import std/cpuinfo
(2000 div countProcessors()) - 2
else:
95

let rdb* = dbopen(
PostgreSQL, # SQLite3 or MySQL or MariaDB or PostgreSQL
url="postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world",
# url="postgresql://user:pass@postgreDb:5432/database",
# url="postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world",
url="postgresql://user:pass@postgreDb:5432/database",
maxConnections=maxConnections,
timeout=30,
shouldDisplayLog=false,
Expand Down
60 changes: 60 additions & 0 deletions scripts/bench_ducere_build_opts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# fixブランチ: ducere build 相当の Nim フラグ比較用(手動実行)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
APP="$ROOT/examples/example"
OUTDIR="${TMPDIR:-/tmp}/basolato_build_bench"
mkdir -p "$OUTDIR"
cd "$APP"
export SECRET_KEY="${SECRET_KEY:-$(grep '^SECRET_KEY=' .env | cut -d= -f2- | tr -d '"')}"

# buildImpl に揃えた共通フラグ(jsBuild 除く)
COMMON=(
--threads:off
-d:ssl
-d:release
--parallelBuild:0
--panics:on
)

elapsed_sec() {
local start end
start="$(date +%s.%N)"
"$@"
end="$(date +%s.%N)"
awk -v a="$start" -v b="$end" 'BEGIN{printf "%.3f", b-a}'
}

run_variant() {
local name="$1"
shift
local out="$OUTDIR/$name"
local i t sum=0
rm -f "$out"
for i in 1 2 3; do
rm -f "$out"
t="$(elapsed_sec nim c -f "${COMMON[@]}" "$@" --out:"$out" main.nim)"
sum="$(awk -v a="$sum" -v b="$t" 'BEGIN{printf "%.6f", a+b}')"
done
rm -f "$out"
nim c -f "${COMMON[@]}" "$@" --out:"$out" main.nim >/dev/null
local bytes
bytes="$(stat -c%s "$out" 2>/dev/null || stat -f%z "$out")"
echo "$name $(awk -v s="$sum" 'BEGIN{printf "%.3f", s/3}') $bytes"
rm -f "$out"
}

echo "# Nim $(nim --version | head -1)"
echo "# cwd $APP"
echo "# 各設定: nim c -f を3回、表示は wall 秒の平均。最後に1回ビルドしてバイナリサイズ取得"
echo "variant avg_compile_sec_s bytes"

# GC + 最適化軸
run_variant "mem_danger_lto" --mm:orc -d:useMalloc -d:danger --passC:"-flto" --passL:"-flto"
run_variant "speed_danger_lto" --mm:markAndSweep -d:useRealtimeGC -d:danger --passC:"-flto" --passL:"-flto"
run_variant "mem_danger_no_lto" --mm:orc -d:useMalloc -d:danger
run_variant "speed_danger_no_lto" --mm:markAndSweep -d:useRealtimeGC -d:danger
run_variant "mem_nodanger_lto" --mm:orc -d:useMalloc --passC:"-flto" --passL:"-flto"
run_variant "speed_nodanger_lto" --mm:markAndSweep -d:useRealtimeGC --passC:"-flto" --passL:"-flto"

# --threads:on は Basolato 標準サーバが GC-safety でコンパイル不能のため計測対象外
2 changes: 1 addition & 1 deletion src/basolato/cli/ducere.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ import ./functions/buildImpl
when isMainModule:
import cligen
clCfg.version = BasolatoVersion
dispatchMulti([newImpl.new], [make], [serve], [build, help=BUILD_HELP])
dispatchMulti([newImpl.new], [make], [serve], [build])
29 changes: 3 additions & 26 deletions src/basolato/cli/functions/buildImpl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import std/os
import std/osproc
import std/strformat
import std/strutils
from std/tables import toTable


proc jsBuild() =
Expand All @@ -19,22 +18,13 @@ proc jsBuild() =
quit(QuitFailure)


const BUILD_HELP* = {
"optimize": "memory|speed"
}.toTable()

proc build*(workers:uint=0, force=false, httpbeast=false, httpx=false, autoRestart=false, optimize="memory", args:seq[string]) =
proc build*(workers:uint=0, force=false, httpbeast=false, httpx=false, autoRestart=false, args:seq[string]) =
## Build for production.
jsBuild()
var outputFileName = "main"
let fStr = if force: "-f" else: ""
let serverStr = if httpbeast: "-d:httpbeast" elif httpx: "-d:httpx" else: ""
let workers = if workers == 0: countProcessors().uint else: workers
let optimize =
if optimize == "speed":
"--mm:markAndSweep -d:useRealtimeGC"
else:
"--mm:orc -d:useMalloc"

if args.len > 0:
outputFileName = args[0]
Expand All @@ -43,7 +33,7 @@ proc build*(workers:uint=0, force=false, httpbeast=false, httpx=false, autoResta
nim c \
{fStr} \
{serverStr} \
{optimize} \
--mm:orc \
--threads:off \
-d:ssl \
-d:danger \
Expand All @@ -52,8 +42,6 @@ proc build*(workers:uint=0, force=false, httpbeast=false, httpx=false, autoResta
--passC:"-flto" \
--passL:"-flto" \
--panics:on \
--stackTrace \
--lineTrace \
--out:{outputFileName} \
main.nim
"""
Expand Down Expand Up @@ -88,16 +76,5 @@ echo "running {workers} {processes}"


#[

--threads:off \
--threadAnalysis:off \
-d:ssl \
-d:release \
-d:danger \
--checks:off \
-d:useMalloc \
-d:useRealtimeGC \
--panics:on \
--gc:orc \

Historical reference only; production flags are assembled in proc build above.
]#
Loading