From 5cba25c90a0facc7c2acf5e3ca608866380e4c35 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Tue, 19 May 2026 15:41:08 +0800 Subject: [PATCH 1/4] =?UTF-8?q?[0054]=20=E6=B7=BB=E5=8A=A0=20inlet=20?= =?UTF-8?q?=E6=80=A7=E8=83=BD=20benchmark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- bench/inlet-bench.scm | 138 ++++++++++++++++++++++++++++++++++++++++++ src/s7.c | 13 ++-- 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 bench/inlet-bench.scm diff --git a/bench/inlet-bench.scm b/bench/inlet-bench.scm new file mode 100644 index 00000000..bfcd1289 --- /dev/null +++ b/bench/inlet-bench.scm @@ -0,0 +1,138 @@ +;; +;; Copyright (C) 2026 The Goldfish Scheme Authors +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +;; WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +;; License for the specific language governing permissions and limitations +;; under the License. +;; + +(import (scheme base) + (scheme time) + (liii timeit) +) ;import + +(define big-inlet-size 6000) + +;; Generate a big inlet with N symbol/value pairs using varlet +(define (make-big-inlet n) + (let ((env (inlet))) + (do ((i 0 (+ i 1))) + ((>= i n) env) + (varlet env (string->symbol (string-append "sym-" (number->string i))) i)))) + +;; --- Performance Test Harness --- +(define (bench-case label thunk iterations) + (let ((t (timeit thunk '() iterations))) + (display label) + (display ": ") + (display t) + (display "s for ") + (display iterations) + (display " iterations") + (newline))) + +(define (run-all-tests) + (display "=== Inlet Performance Benchmark ===") + (newline) + (display "Big inlet size: ") + (display big-inlet-size) + (newline) + (newline) + + ;; 1. Empty inlet -- goes through s7_inlet -> sublet_1 + (bench-case "empty inlet" + (lambda () (inlet)) + 1000000) + + ;; 2. simple_inlet path -- 2 args + (bench-case "simple inlet 2 args" + (lambda () (inlet 'a 1)) + 1000000) + + ;; 3. simple_inlet path -- 8 args + (bench-case "simple inlet 8 args" + (lambda () (inlet 'a 1 'b 2 'c 3 'd 4 'e 5 'f 6 'g 7 'h 8)) + 1000000) + + ;; 4. generic path -- with cons args (goes through s7_inlet -> sublet_1) + (bench-case "generic inlet (with cons)" + (lambda () (inlet '(a . 1) '(b . 2))) + 1000000) + + ;; 5. generic path -- with let arg (goes through s7_inlet -> sublet_1) + (let ((e (inlet 'x 1 'y 2))) + (bench-case "generic inlet (with let)" + (lambda () (inlet 'a 1 e)) + 1000000)) + + (newline) + (display "=== Big Inlet Tests ===") + (newline) + (newline) + + ;; 6. Create big inlet incrementally via sublet + (bench-case (string-append "create big inlet (" (number->string big-inlet-size) " via sublet loop)") + (lambda () (make-big-inlet big-inlet-size)) + 10) + + ;; Pre-create big inlet for subsequent tests + (define big-inlet (make-big-inlet big-inlet-size)) + (display "Big inlet created, slot count: ") + (display (length (let->list big-inlet))) + (newline) + (newline) + + ;; 7. Copy big inlet via inlet (triggers append_let in sublet_1) + (bench-case "copy big inlet via (inlet big-inlet)" + (lambda () (inlet big-inlet)) + 100) + + ;; 8. Copy big inlet via sublet + (bench-case "copy big inlet via (sublet big-inlet)" + (lambda () (sublet big-inlet)) + 100) + + ;; 9. Merge big inlet with small inlet + (bench-case "merge big inlet + small inlet" + (lambda () (inlet 'a 1 big-inlet)) + 100) + + (newline) + (display "=== Big Inlet Symbol Lookup Tests ===") + (newline) + (newline) + + ;; 10. defined? on existing symbol in big inlet + (bench-case "defined? existing symbol in big inlet" + (lambda () (defined? 'sym-100 big-inlet)) + 100000) + + ;; 11. defined? on non-existing symbol in big inlet + (bench-case "defined? non-existing symbol in big inlet" + (lambda () (defined? 'nonexistent big-inlet)) + 100000) + + ;; 12. let-ref on existing symbol in big inlet + (bench-case "let-ref existing symbol in big inlet" + (lambda () (let-ref big-inlet 'sym-100)) + 100000) + + ;; 13. let-ref on last symbol in big inlet + (let ((last-sym (string->symbol (string-append "sym-" (number->string (- big-inlet-size 1)))))) + (bench-case "let-ref last symbol in big inlet" + (lambda () (let-ref big-inlet last-sym)) + 100000)) + + (newline) + (display "=== Benchmark completed ===") + (newline)) + +(run-all-tests) diff --git a/src/s7.c b/src/s7.c index cf7295bf..7f514053 100644 --- a/src/s7.c +++ b/src/s7.c @@ -10341,8 +10341,11 @@ static s7_pointer sublet_1(s7_scheme *sc, s7_pointer let, s7_pointer bindings, s case T_LET: if ((entry == sc->rootlet) || (new_let == sc->starlet)) continue; append_let(sc, new_let, entry); - if (is_not_slot_end(let_slots(new_let))) /* make sure the end slot (slot) is correct */ - for (slot = let_slots(new_let); is_not_slot_end(next_slot(slot)); slot = next_slot(slot)); /* slot can't be local -- see below */ + if (is_pair(cdr(entries))) /* only need tail if more entries follow */ + { + if (is_not_slot_end(let_slots(new_let))) /* make sure the end slot (slot) is correct */ + for (slot = let_slots(new_let); is_not_slot_end(next_slot(slot)); slot = next_slot(slot)); /* slot can't be local -- see below */ + } continue; default: @@ -10420,12 +10423,14 @@ static s7_pointer sublet_chooser(s7_scheme *sc, s7_pointer func, int32_t num_arg /* -------------------------------- inlet -------------------------------- */ s7_pointer s7_inlet(s7_scheme *sc, s7_pointer args) { - #define H_inlet "(inlet ...) adds its arguments, each a let, a cons: '(symbol . value), or a symbol/value pair, \ -to a new let, and returns the new let. (inlet :a 1 :b 2) or (inlet 'a 1 'b 2)" + #define H_inlet "(inlet ...) adds its arguments, each a let, a cons: '(symbol . value), or a symbol/value pair, \nto a new let, and returns the new let. (inlet :a 1 :b 2) or (inlet 'a 1 'b 2)" #define Q_inlet s7_make_circular_signature(sc, 1, 2, sc->is_let_symbol, sc->T) + if (args == sc->nil) + return(make_let(sc, sc->rootlet)); return(sublet_1(sc, sc->rootlet, args, sc->inlet_symbol)); } + #define g_inlet s7_inlet static s7_pointer g_simple_inlet(s7_scheme *sc, s7_pointer args) From 1a42972e6f01b9b43aebc0336291684e9506aa01 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Tue, 19 May 2026 15:41:22 +0800 Subject: [PATCH 2/4] =?UTF-8?q?[0054]=20=E4=BC=98=E5=8C=96=20inlet=20?= =?UTF-8?q?=E4=B8=AD=20append=5Flet=20=E5=90=8E=E7=9A=84=E4=BA=8C=E6=AC=A1?= =?UTF-8?q?=E9=81=8D=E5=8E=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sublet_1 的 T_LET 分支中,仅在后面还有 entries 时才遍历找尾 - s7_inlet 添加空参数快速路径,直接返回 make_let 性能变化(6000 slot 大 inlet,100 次迭代): - copy big inlet: 0.0140s -> 0.0127s (-9%) - merge big inlet: 0.0119s -> 0.0105s (-12%) Co-Authored-By: Claude Opus 4.7 --- devel/0054.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/s7.c | 10 +++++----- 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 devel/0054.md diff --git a/devel/0054.md b/devel/0054.md new file mode 100644 index 00000000..15675961 --- /dev/null +++ b/devel/0054.md @@ -0,0 +1,40 @@ +# [0054] 优化 inlet 性能 + +## 任务相关的代码文件 +- `src/s7.c` +- `bench/inlet-bench.scm` + +## 如何测试 +```bash +# 1. 构建 +xmake b goldfish + +# 2. 运行 inlet 性能测试 +bin/gf bench/inlet-bench.scm +``` + +## 2026-05-19 优化 sublet_1 中 append_let 后的二次遍历 + +### What +在 `sublet_1` 函数的 `T_LET` 分支中,`append_let` 复制完一个 let 的所有 slot 后,原来 unconditionally 地遍历链表找到最后一个 slot。这个遍历在 `append_let` 是当前 bindings 的最后一个元素时是不必要的,因为后面没有更多的 slot 需要追加。 + +修改:添加 `if (is_pair(cdr(entries)))` 判断,仅在后面还有 entries 需要处理时才执行找尾遍历。 + +### Why +当 inlet 参数包含一个大的 let(如 6000 个 slot)且该 let 是最后一个参数时,`append_let` 已经遍历了 6000 次复制 slot,之后还要再遍历 6000 次找尾。对于 `(inlet big-let)` 这种常见模式,这是纯粹的冗余开销。 + +### How +- `src/s7.c` 的 `sublet_1` 函数中,`case T_LET` 分支内添加条件判断 +- 同时优化 `s7_inlet` 的空参数路径:`args == sc->nil` 时直接 `make_let` 返回,跳过 `sublet_1` 调用 + +### Benchmark 结果 +使用 `bench/inlet-bench.scm` 测试(6000 slot 的大 inlet,100 次迭代取平均): + +| 测试项 | 优化前 | 优化后 | 变化 | +|---|---|---|---| +| copy big inlet via (inlet big-inlet) | ~0.0140s | ~0.0127s | -9% | +| merge big inlet + small inlet | ~0.0119s | ~0.0105s | -12% | +| empty inlet | ~0.0189s | ~0.0190s | 持平 | +| simple inlet 2 args | ~0.0313s | ~0.0315s | 持平 | + +结论:在大 let 复制和合并场景下有稳定提升,小 inlet 场景无负面影响。 diff --git a/src/s7.c b/src/s7.c index 7f514053..ae9ddb84 100644 --- a/src/s7.c +++ b/src/s7.c @@ -10341,11 +10341,11 @@ static s7_pointer sublet_1(s7_scheme *sc, s7_pointer let, s7_pointer bindings, s case T_LET: if ((entry == sc->rootlet) || (new_let == sc->starlet)) continue; append_let(sc, new_let, entry); - if (is_pair(cdr(entries))) /* only need tail if more entries follow */ - { - if (is_not_slot_end(let_slots(new_let))) /* make sure the end slot (slot) is correct */ - for (slot = let_slots(new_let); is_not_slot_end(next_slot(slot)); slot = next_slot(slot)); /* slot can't be local -- see below */ - } + if (is_pair(cdr(entries))) /* only need tail if more entries follow */ + { + if (is_not_slot_end(let_slots(new_let))) /* make sure the end slot (slot) is correct */ + for (slot = let_slots(new_let); is_not_slot_end(next_slot(slot)); slot = next_slot(slot)); /* slot can't be local -- see below */ + } continue; default: From 5006830f0830b186d6c8bb49cdf5fbc9e31084fc Mon Sep 17 00:00:00 2001 From: Da Shen Date: Tue, 19 May 2026 15:50:07 +0800 Subject: [PATCH 3/4] =?UTF-8?q?[0054]=20=E8=B0=83=E6=95=B4=20inlet=20bench?= =?UTF-8?q?mark=20=E8=BF=AD=E4=BB=A3=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将各测试项运行时间控制在 0.01s ~ 1s 之间,减少测量噪声。 Co-Authored-By: Claude Opus 4.7 --- bench/inlet-bench.scm | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bench/inlet-bench.scm b/bench/inlet-bench.scm index bfcd1289..9bffd6cf 100644 --- a/bench/inlet-bench.scm +++ b/bench/inlet-bench.scm @@ -50,12 +50,12 @@ ;; 1. Empty inlet -- goes through s7_inlet -> sublet_1 (bench-case "empty inlet" (lambda () (inlet)) - 1000000) + 10000000) ;; 2. simple_inlet path -- 2 args (bench-case "simple inlet 2 args" (lambda () (inlet 'a 1)) - 1000000) + 5000000) ;; 3. simple_inlet path -- 8 args (bench-case "simple inlet 8 args" @@ -65,13 +65,13 @@ ;; 4. generic path -- with cons args (goes through s7_inlet -> sublet_1) (bench-case "generic inlet (with cons)" (lambda () (inlet '(a . 1) '(b . 2))) - 1000000) + 2000000) ;; 5. generic path -- with let arg (goes through s7_inlet -> sublet_1) (let ((e (inlet 'x 1 'y 2))) (bench-case "generic inlet (with let)" (lambda () (inlet 'a 1 e)) - 1000000)) + 2000000)) (newline) (display "=== Big Inlet Tests ===") @@ -81,7 +81,7 @@ ;; 6. Create big inlet incrementally via sublet (bench-case (string-append "create big inlet (" (number->string big-inlet-size) " via sublet loop)") (lambda () (make-big-inlet big-inlet-size)) - 10) + 200) ;; Pre-create big inlet for subsequent tests (define big-inlet (make-big-inlet big-inlet-size)) @@ -93,17 +93,17 @@ ;; 7. Copy big inlet via inlet (triggers append_let in sublet_1) (bench-case "copy big inlet via (inlet big-inlet)" (lambda () (inlet big-inlet)) - 100) + 1000) - ;; 8. Copy big inlet via sublet + ;; 8. Copy big inlet via sublet (no actual copy, just makes empty child let) (bench-case "copy big inlet via (sublet big-inlet)" (lambda () (sublet big-inlet)) - 100) + 1000000) ;; 9. Merge big inlet with small inlet (bench-case "merge big inlet + small inlet" (lambda () (inlet 'a 1 big-inlet)) - 100) + 1000) (newline) (display "=== Big Inlet Symbol Lookup Tests ===") @@ -113,23 +113,23 @@ ;; 10. defined? on existing symbol in big inlet (bench-case "defined? existing symbol in big inlet" (lambda () (defined? 'sym-100 big-inlet)) - 100000) + 50000) ;; 11. defined? on non-existing symbol in big inlet (bench-case "defined? non-existing symbol in big inlet" (lambda () (defined? 'nonexistent big-inlet)) - 100000) + 10000000) ;; 12. let-ref on existing symbol in big inlet (bench-case "let-ref existing symbol in big inlet" (lambda () (let-ref big-inlet 'sym-100)) - 100000) + 50000) ;; 13. let-ref on last symbol in big inlet (let ((last-sym (string->symbol (string-append "sym-" (number->string (- big-inlet-size 1)))))) (bench-case "let-ref last symbol in big inlet" (lambda () (let-ref big-inlet last-sym)) - 100000)) + 10000000)) (newline) (display "=== Benchmark completed ===") From 3462b14ed10cafdd66181ff511b41cefb3091a56 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Tue, 19 May 2026 16:01:20 +0800 Subject: [PATCH 4/4] =?UTF-8?q?[0054]=20=E6=B7=BB=E5=8A=A0=20define-record?= =?UTF-8?q?-type=20=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 inlet benchmark 中增加不同字段数量(2/4/16)的 record 创建、字段访问、类型判断及与直接 inlet 的对比测试。 Co-Authored-By: Claude Opus 4.7 --- bench/inlet-bench.scm | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/bench/inlet-bench.scm b/bench/inlet-bench.scm index 9bffd6cf..964bc1e4 100644 --- a/bench/inlet-bench.scm +++ b/bench/inlet-bench.scm @@ -131,6 +131,82 @@ (lambda () (let-ref big-inlet last-sym)) 10000000)) + (newline) + (display "=== define-record-type Tests ===") + (newline) + (newline) + + ;; Define record types with different field counts + (define-record-type :point + (make-point x y) + point? + (x point-x) + (y point-y)) + + (define-record-type :person + (make-person name age city job) + person? + (name person-name) + (age person-age) + (city person-city) + (job person-job)) + + (define-record-type :big-record + (make-big-record f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16) + big-record? + (f1 big-record-f1) + (f2 big-record-f2) + (f3 big-record-f3) + (f4 big-record-f4) + (f5 big-record-f5) + (f6 big-record-f6) + (f7 big-record-f7) + (f8 big-record-f8) + (f9 big-record-f9) + (f10 big-record-f10) + (f11 big-record-f11) + (f12 big-record-f12) + (f13 big-record-f13) + (f14 big-record-f14) + (f15 big-record-f15) + (f16 big-record-f16)) + + ;; 14. Create simple record (2 fields + type tag = 4 inlet args) + (bench-case "create record 2 fields" + (lambda () (make-point 1 2)) + 2000000) + + ;; 15. Create medium record (4 fields + type tag = 8 inlet args) + (bench-case "create record 4 fields" + (lambda () (make-person "Alice" 30 "NYC" "Dev")) + 1000000) + + ;; 16. Create big record (16 fields + type tag = 32 inlet args) + (bench-case "create record 16 fields" + (lambda () (make-big-record 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16)) + 500000) + + ;; 17. Access record field (let-ref underlying) + (define test-person (make-person "Alice" 30 "NYC" "Dev")) + (bench-case "access record field (let-ref)" + (lambda () (person-name test-person)) + 20000000) + + ;; 18. Record type predicate + (bench-case "record type predicate" + (lambda () (person? test-person)) + 20000000) + + ;; 19. Compare: direct inlet vs record create (2 fields) + (bench-case "direct inlet 4 args (equiv to record 2f)" + (lambda () (inlet 'type 'point 'x 1 'y 2)) + 2000000) + + ;; 20. Compare: direct inlet vs record create (4 fields) + (bench-case "direct inlet 8 args (equiv to record 4f)" + (lambda () (inlet 'type 'person 'name "Alice" 'age 30 'city "NYC" 'job "Dev")) + 1000000) + (newline) (display "=== Benchmark completed ===") (newline))