From b8d6afdeb3b35c6ea1b22a7cbf49c4ee10d3f7be Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Thu, 16 Apr 2026 20:49:32 -0400 Subject: [PATCH] heap-snapshot + native HTTP client + MOAD-0001 cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three wins in one commit. 1) heap-snapshot / heap-restore (asm arena primitive) asm has no GC. Long-running servers leaked ~64 MB per heap growth. Two new builtins let a programmer capture r15 and later rewind to it, recycling intermediate allocations in O(1) memory. Python + C get no-op versions so portable .lsp code can call them unconditionally. examples/http-server.lsp now takes a snapshot at top level and rewinds after every request. Measured asm RSS: 88 KB initial, 100 KB after 100 requests, 100 KB after 1100 requests — flat. Prior behavior was +64 MB per few thousand requests. 2) examples/http-client-bench.lsp — native HTTP load generator Uses only the six tcp-* primitives + current-time-ms. Runs identically in all three impls. Eliminates curl's ~2 ms/req fork+exec overhead, so real server throughput shows up: Python server ← Python client 2403 rps C server ← C client 2439 rps asm server ← asm client 2994 rps asm server ← C client 2500 rps The earlier curl-based bench was clamped near 400 rps by the client; the actual servers handle 6–7× that. 3) MOAD-0001 cleanup - c/builtins.c bi_string_replace: strncmp-at-every-position (hand-rolled, sedimentary) → strstr (libc-tuned, typically Boyer-Moore-Horspool). O(N*k) → O(N + matches*k). - uncommonlisp.py _tokenize_lines: per-token src.count('\n', 0, pos) → precompute line_starts once, bisect_right per token. O(N*M) → O(M + N log M). Also adds current-time-ms to all three impls so benchmarks can time themselves without relying on the Python/C float `current-time` (asm has no floats). Seconds-since-epoch tagged as a 61-bit int. Test counts unchanged: 571 py + 132 asm + 189 shared + 83 c = 975. All green via make test-all. --- asm/uncommonlisp | Bin 45536 -> 46056 bytes asm/uncommonlisp.o | Bin 43952 -> 44728 bytes asm/uncommonlisp.s | 78 ++++++++++++++++++++++++++++++++- c/builtins.c | 48 ++++++++++++++++---- examples/http-client-bench.lsp | 66 ++++++++++++++++++++++++++++ examples/http-server.lsp | 58 ++++++++++++++---------- uncommonlisp.py | 23 +++++++++- 7 files changed, 239 insertions(+), 34 deletions(-) create mode 100644 examples/http-client-bench.lsp diff --git a/asm/uncommonlisp b/asm/uncommonlisp index 7ebf6a24ba2f551b84ae2da3cf896f7920a91535..600d4811edf1d9e1e168256c91ba2aee0127cd3b 100755 GIT binary patch literal 46056 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 diff --git a/asm/uncommonlisp.o b/asm/uncommonlisp.o index c7efe25782f6834ccefaf08eff3e603799fd5311..84204d366e9c685463787cb0d6cf2026002788a2 100644 GIT binary patch literal 44728 zcmeI533Sxe+5dkb3t=5Zi>O$mpnw_#Q3Q!30V2O>LRL1nagt16O31`y!eUXe8D1G9 z+C}Tq+S=B&oBGyTwb~L80$PhpTX6-K>cqH3t43|j|NA`mxii0clGx=v=RN0t&O7HY zzvq7LbNB7JcYe8CR#bB4khC<%e$t$IPSRzmA=DW7Tjg6ut;w>}rHUAW7wVgr0iF_6D znCLqBvGr8KD8y(ye3|jTqcDgglJBChlOWn>+s?2x|+Uf$o8=LLMnzG%t-D&Wvgy(qj zEQ04bN#9uc7-*#LkXRsRc(o7hrwz^bsVc{~b7n`f-1dSo-S6W&dj}O=+8{OChjF%% zpI*1^K(Fh&e~i1eDJ@kS#TX>Csrvqfev&fL?#%=WyQ8YGdWjqRyg2qZgvdmNxij{Z z%z5RKL*3YRx8v1JH|&4!gNplhbiZ8^+l?w4a(>}?=k6#)Ep%@$ z-nwy8T73UM`uY}b$tj(&t#r>%t-IH zjd(L+SmDO96S{}I=f=vjp^RrTQIzI1ck5+oT|>}PZo3(AbNR8c(2kGRB~_>nGy@N` zM1OKHx_p`Eq8~b!&KzKv&e&#i=(|57&B}?KQa?qaeLDtCcHuuw_LzT~Y$BDa__b6- z8Z^`Law22%hDAo_4UY`Z8x_gQn;RL{JwE>TFZ%kr_r*WLgY~lVhPU=~cb-e#BK|N! z$17|UbMmJ6qUWQd=1q;9k~cYWdfqX<=)LG{c@rb&=jHmMTTokh(|uP>BsRx)^~-Q~ zjf2CTv5i_gehpHA(EXG6W=QcHDdrePM<=FGs{i=vzZ2QzwsX&lp9d!gTuSN{_{xh! ze0lYeaj3^55r8})QlAi!20RCuNI|Z{7Z)o#Z~DfJyg8R1DwE}|j7hm}=MLn}>H0ms z+!-&#S6Su5p*${c&ShicFQZ4fZSm>u>KBH)X*;@Ka6fs$jqQ*B_H)VDBUT}{BR-{n z_#asBM0f(9} zCb;NBmw)~lvVKWM_wTzOaW}t!T8X{s#$JvePqk~P5ObBW;A zDtGJGv*YQX_4S?W#>Qi6apydD$;c}Y8M&o8JDcW{_w16@wmXX(?rTUfo*~Uhy>J#X z8-HA~=!^alUU^e}(GGe}i%f`DQeja4PjYwKMeQ>Y>2YL7Qe@Xq8FC^QpZl%Zs_#%==g>d!5Lc_|A-!<9moN`Z&}erTL<( zXjrxNru(AL8HpU-lQrhmQT>3IhO7R1`C8Hq5`2oAEVy?r5W_rbZkdS z`^3T2si6?5S^o3f&3hs_6@%%T8eLI=0qVAId^|foANfE^D`-sr{>wvAhN`HKZ{D_H zV)>WrE_XlK=WeCB9^HLM{;qT~N?S2=JiaS^diMmfXbyE}JneRNP=9bg>Djb>Iy|== z3`0eX>9%e8?$mu`&BR_#O$;mfj;bXnLF^;Xkn=@f<<{Xq)C-3AHeZ90?#|`%_@X;W*E%}A4qi2_&!1_m zTgMEvQo>Q}b+mdNGckiE)GqfrcEZt>P1%U`7M^!L7SPzcaWntG0h8Dmt0TxxJj@uY zB?gW4nvSsCF7WTTz~6P+M}2)Iu>(Ai+cDQ5l7w?MnYqS?tAct9Qrm)If=+-M`ATFo zzB41Jr2Sp%fs5Pzo*D5aBj~E7Rn;zC-ndoWrbP-k?6_oD^s#s26A=E~?x7L|l7W3u z68j*2*58mArfW~siIJS`wwGsDRF=fvj8Ep~+~s#;Z^gSlqQ?q z$h_~m9;qej1SMOaU18TMF4e*C=T_tDo19RcO?{WyXwzy>WBn>Tulwb$Cuvpl)LSBm zYLX$5IyctEQ=yxvTXYFbk7j_Bv}3Ba9b|S@MPWI$7#RSC%U7IRRYLn_l>f2#%^y(B zcbwb(-)`&)YL;(OuZVSa6+t(Vd^Y76U98xwr^THe)UBy4BDK5$z*edq>7#w@0ENq@ zKOM;zRxER4pHL?LignpvxB~eouPCf2iS3H-?V%2{t#HMP<(yIMhyNIVh+zlRy00g- z&iHc^aRw0E=8r$gLI?VJ6-7+BexBy)*sj7Aey|E^*zY2I;fizNpKR=Vz_7h4T(KNQ zcH3U*bz`A1R&1YB(jGwm-z|ylaohSbBlEd@dCMv=aHaoY6N}vr6?XbLmGJ;aw`=(d ze?nnBA`lHXyZZr5f5=_er?6+Zy^9ky>S@qK)y-wwJTjre^oobs)LOw~8M(z+{%toZ z;16BDFbk70(=l*4>Y0hClillZ6HyCC#PJ-)@f_U!bXSmUENkE+63E8Fw-L@7Ai^U} zgvTT!e4Zox6L<5jgG`)>cu?~uKo6U3u-8x<)CXMjp5oY}$@v?T|6AOndeb8pBP(oc zbarCi^&B}I3K!cNeC%P59MCPX%l?R)3>!yjDXHK6lv)c?FPD`6XiQmu{G9jt`go^| z)TlEXIbkbsUv6(214D*mUg)-cmKmA1tfDfdH$0i_4ONH`U2yq1sym2Ed3N{xyc+K9 zdaIW-6IP-x+6hS$e=M#7pIGSV1d^adjx`zUU~N{6TMqFM5`dPDX1PX(rm&$G)fdqPH38RFn;8X9zJ1xiZpe z2y3L-zUVq5osM+Jvff;j$VhpxqKtHgFZ!I3<{2eWUX zFY20@=lh~fMq1#D`i<{Gw6tMGzUaF~I@1^Zx{(&aW;1%OFZ%OCIKE5INS zfmxL=T91hkA*y}RpPDq5_@XzNSe9aZ7-^X=`Upv$#TDi_&zy(KM8_5-*3FLIbT_S| z`!zSVH-2KbEa3y_#5y?mk^Fu5018tnl24wacwT=?;(<9$a~p>lKR_7Y-MhPAr4av} z3?Zj5YLZR~ob7genw~;TIp%akXv>h8kyBI#547>h{~(a>nhKg+|NYc_SC9V>18&`K zY}kRzH#?=Y)8i|<25QXNbZ+jp5B1vDrYnfgqU`=&)Zs7z&N4#^OgX3PxQ`ee3_BL^Kh@Bzcdp>8f6G|-ApUCr9UrcC4Gzofc0 zhrKY3Fxz8PB=%8Rk>Oah<9px5+2vQsaN@c-)ZMyHodEbUVMCd~o*`w-UlsY6ZlMQ? z1tslY`2VTazmG#^O(U-Pp74 z=05DGV!b6eh{o}ExAKIqbkWR-CuRA-*;r=U398WKHK*fJOw=9SFHwBy1!4u34tkj8V=%eI@3WjL2BjU3%mz$TIC+5*9b6 zuI@l$8_P!oZu=^j-f!>@Y?j;hMP_7rVq-Pi>@&HimCz=$eR^SeMI~+e_P&M+iS0aR z#Rc8iTs%w0|Jrw~oo_7et{Bc2Qd+D3cpc+*e1=}P+l@Wt-spbvDfQ1|5Ox1q$<)AI zG|&1rSCU;nSaWzaqf@?2Qw>#~^j0s$uU9*0(87SgH`W$Rg#|M7a0W8l{?PM{SCNU$ zdvK74J`>x~jX4W@v{x~O%?H}y_l(x=hosWay?}q5jqNrwLhSwi1)Uu~2S#0go@&Ej z#e5qJMaCq~DP?gXw*86uLw<|e{|dFzE*!_~k3VCMV>;dT$fWG}PWD+iDLW;b_xs~7 z!M6PO6E909o{hC`udSmCJQ<+hIB>Q~2WDeQh4XR7fcf~DKfdH&$VckjY^o$SauT;# zaWY4<@f)@IZEo)Yt`D5{P8t*6ZX8@W_hSjbS>B^)I9(_AF8>#*1NDEZCqbK@A4y>M zO-W#k#mgy`lnUP|BlMpq5!-KL8BVeE574hNkf#~8+g4D&#aYjK%pTY^V~UN8N_3a? z+1*dU!BGx6ss@UFRw9ZqbSCGIVtQz#) zyRcL&AHWJqnO(>V#DWMLt26%c%LC6Lq!H^-!LYjd?%$Y(6808%4y75UaLZ*lHtZaO zANlcv9<~EGT)|HYbV7{7p+hmc-L}_|)?BvQ(tU3Gf^6ExJWl<(tus6RmzSuc?Q~=3 zJ0)97Vb(6Oflu-TMkr(U`AW7f%cdVIUM#wDNYNI&FR;BBKe+ep>3-{cHx^H1BPHYg zW-l1mZEqk~K6mbD-{#Zlgm&v?c#~i)MeU0o32^1m)3bb=k0P(WPPcCtCGl!XtY-E! zuSK9J`MZXqLchQp4P=WA$FU^U4_OlZez4fP_u*}M?81F!zeZDXOC zhPl4G@jlL;?hjx=#7}r(;33QPtE1tK0rkSod&sVRj#_;*b;(b9j5X2y4ow5kKA(v5 zP1@B^=`euD@OwAgd9iMvpWXcyjr^FebnC{w=$W5!_o72c4D_e?@VXrn>1i~u-S!F^ z=xw`)7I!|Hua(XtCDc;=UF@eW#dM;6Up7_X{S{_H9f56_g z>lu6v6donrxErbP!b{CF-fep?8^2Ao=f_>d470?KA6oa&v^kI!gh}ULZ84e+Vju9a z@t3t25A*@DN^b?G6c9abz(vBHy4$p5F>TVfZuBA94l0`O%As2@|n0lX)O-+-uj&03O@8aEhC*)&tzmmuG$7%0|VS)9Jh1f zq$vnZ$9FuZ($9BqAd%@{NTlu2=^mfcC zXfA=3ieEUWYEbqHdb5pR^u(4&zcPC=qQLry{knN&R_qm+yve+n8pF?l{i=&L@MbA8 zb?mLm|E20%h!&@A)42^N|J05yz2m!`Ivlb~9gBX_FDEJbHfbnDAoHsc*AU+A#Sg_Y z-Q$g7il#WWkKZQ7{Lv>bA;ic8P8Qa@%2+NPwf{4gOR0F@DsEqhwy_6fG$lL8q!V}c zVUB?%0yCH2`rQYMu)E6|k%K)iKxXih1PcEiw@|VZ)84?FQP1?r`-W7%M2ihFkW-In z2A%}s)RmCtKOOMv4|X%@9FpD##w>%^lD_N!8)c(kWHB@0_x-PWPBX`v(@fNt6g0I4 zQl_nxNXC028HupTuKe$f{PSCY}9eqf&ipr=hZ}cxB_HN zkruN$iS7r(X0T;X-WJX-nbW&2onG%x>CtbGf%|nzwzJcu{suBhvn5ZGZI6y|L))m+ z$zdz+1`6i;==G(Nv~AQCO*e$emC`wPBuz{{PsdyA1D&VSESlQHW@Ca%?A$ihW$zo5gTHrfdUbGI|x$HGaS~3mZ0jZ;z=*m>~!sua9-q zcXVXeV~rewHIcMX6*wC(!nPUYwu=jXn6q)9g^xuJX)Pe{&Rv^~A2?>B{qeWak?i

5tvO?mM)PeOx4o-%?G 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 literal 43952 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 diff --git a/asm/uncommonlisp.s b/asm/uncommonlisp.s index 312341a..54c0c39 100644 --- a/asm/uncommonlisp.s +++ b/asm/uncommonlisp.s @@ -35,6 +35,8 @@ .equ SYS_LISTEN,50 .equ SYS_SETSOCKOPT,54 .equ SYS_EXIT, 60 +.equ SYS_CLOCK_GETTIME, 228 +.equ CLOCK_REALTIME, 0 .equ AF_INET, 2 .equ SOCK_STREAM,1 @@ -160,7 +162,10 @@ .equ BI_TCPRECV, 82 .equ BI_TCPSEND, 83 .equ BI_TCPCLOSE, 84 -.equ BI_COUNT, 85 +.equ BI_HEAPSNAP, 85 +.equ BI_HEAPREST, 86 +.equ BI_CURTIME, 87 +.equ BI_COUNT, 88 # ============================================================ .data @@ -271,6 +276,9 @@ bn_tcpconnect: .byte 11; .ascii "tcp-connect" bn_tcprecv: .byte 8; .ascii "tcp-recv" bn_tcpsend: .byte 8; .ascii "tcp-send" bn_tcpclose: .byte 9; .ascii "tcp-close" +bn_heapsnap: .byte 13; .ascii "heap-snapshot" +bn_heaprest: .byte 12; .ascii "heap-restore" +bn_curtime: .byte 15; .ascii "current-time-ms" portal_magic: .ascii "ULPORTAL" .equ PORTAL_MAGIC_LEN, 8 @@ -300,6 +308,7 @@ bi_names: .quad bn_writefile, bn_filetostr .quad bn_tcplisten, bn_tcpaccept, bn_tcpconnect .quad bn_tcprecv, bn_tcpsend, bn_tcpclose + .quad bn_heapsnap, bn_heaprest, bn_curtime # Error messages err_unbound: .ascii "Error: unbound variable: " @@ -2488,6 +2497,12 @@ eval_list: je bi_tcp_send cmpq $BI_TCPCLOSE, %rax je bi_close_port + cmpq $BI_HEAPSNAP, %rax + je bi_heap_snapshot + cmpq $BI_HEAPREST, %rax + je bi_heap_restore + cmpq $BI_CURTIME, %rax + je bi_current_time_ms movq $VAL_VOID, %rax popq %r12 @@ -4399,6 +4414,67 @@ bi_file_to_string: movq $VAL_FALSE, %rax RET_VAL +# ============================================================ +# Heap snapshot/restore: UNSAFE escape hatch for long-running loops. +# asm has no GC. A server that bump-allocates every request leaks +# ~64 MB per heap_grow forever. heap-snapshot captures r15; a later +# heap-restore rewinds r15 to that point, reclaiming everything +# allocated since. +# +# DANGER: any Scheme value that lives past the restore point but was +# allocated after the snapshot becomes a dangling pointer. Use only +# when the programmer can prove no such references exist — the +# canonical pattern is a per-request scope in a server loop: +# +# (let ((snap (heap-snapshot))) +# (loop ...) +# (heap-restore snap)) +# +# Top-level bindings stay alive because they allocate before the snap. +# ============================================================ + +bi_heap_snapshot: + # Return r15 as a tagged int (61-bit payload fits a 64-bit pointer + # because all asm heap addresses have 0 in the low 3 bits already). + movq %r15, %rax + # Low 3 bits of r15 are 0 (8-aligned). TAG_INT = 0, so no OR needed. + RET_VAL + +bi_heap_restore: + GETARG %rax + # Untag: caller passed a heap-snapshot int. Clear tag bits just in case. + andq $-8, %rax + # Sanity: don't rewind past heap_base or forward past current r15. + cmpq heap_base(%rip), %rax + jl .hr_noop + cmpq %r15, %rax + ja .hr_noop + movq %rax, %r15 +.hr_noop: + movq $VAL_VOID, %rax + RET_VAL + +# bi_current_time_ms: (current-time-ms) → int ms since epoch +# struct timespec is { int64_t tv_sec; int64_t tv_nsec; } — 16 bytes. +bi_current_time_ms: + subq $16, %rsp + movq $SYS_CLOCK_GETTIME, %rax + movq $CLOCK_REALTIME, %rdi + movq %rsp, %rsi + syscall + movq (%rsp), %rax # tv_sec + movq 8(%rsp), %rcx # tv_nsec + addq $16, %rsp + imulq $1000, %rax # sec * 1000 + movq %rax, %r8 # stash + movq %rcx, %rax # rax = tv_nsec + xorq %rdx, %rdx + movq $1000000, %rcx + divq %rcx # rax = tv_nsec / 1e6 + addq %r8, %rax # total ms + shlq $3, %rax # tag as int + RET_VAL + # ============================================================ # TCP sockets: fd encoded as port (same as file ports). # tcp-recv = sys_read, tcp-send = sys_write, tcp-close = close-port. diff --git a/c/builtins.c b/c/builtins.c index bf2bf46..10fa2df 100644 --- a/c/builtins.c +++ b/c/builtins.c @@ -1204,6 +1204,28 @@ static Value bi_tcp_close(Value *a, int n, Env *e) { p->closed = true; return VAL_VOID; } + +/* heap-snapshot / heap-restore: asm-only arena primitives. The asm impl + * has no GC; these let a server rewind its bump allocator between + * requests. Python + C have real GCs — no-ops here so portable .lsp + * code can call them unconditionally. */ +static Value bi_heap_snapshot(Value *a, int n, Env *e) { + (void)a; (void)n; (void)e; + return VAL_FALSE; +} +static Value bi_heap_restore(Value *a, int n, Env *e) { + (void)a; (void)n; (void)e; + return VAL_VOID; +} + +#include +static Value bi_current_time_ms(Value *a, int n, Env *e) { + (void)a; (void)n; (void)e; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + int64_t ms = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + return VAL_INT(ms); +} static Value bi_open_output_string(Value *a, int n, Env *e) { (void)e; return make_string_output_port(); @@ -1528,19 +1550,24 @@ static Value bi_string_replace(Value *a, int n, Env *e) { const char *from = AS_STRING(a[1])->data; const char *to = AS_STRING(a[2])->data; size_t from_len = strlen(from), to_len = strlen(to); + if (from_len == 0) return a[0]; + /* Use strstr (libc-tuned, often Boyer-Moore-Horspool) to skip the + * O(N*k) hand-rolled strncmp-at-every-position sediment. */ char buf[8192]; size_t pos = 0; - while (*src && pos < sizeof(buf) - to_len - 1) { - if (strncmp(src, from, from_len) == 0) { - memcpy(buf + pos, to, to_len); - pos += to_len; - src += from_len; - } else { - buf[pos++] = *src++; - } + const char *cur = src; + const char *hit; + while ((hit = strstr(cur, from)) != NULL) { + size_t chunk = (size_t)(hit - cur); + if (pos + chunk + to_len >= sizeof(buf) - 1) break; + memcpy(buf + pos, cur, chunk); pos += chunk; + memcpy(buf + pos, to, to_len); pos += to_len; + cur = hit + from_len; } - while (*src && pos < sizeof(buf) - 1) buf[pos++] = *src++; + size_t tail = strlen(cur); + if (pos + tail >= sizeof(buf)) tail = sizeof(buf) - pos - 1; + memcpy(buf + pos, cur, tail); pos += tail; buf[pos] = '\0'; return make_string_from_cstr(buf); } @@ -1836,6 +1863,9 @@ Env *make_global_env(void) { DEF("tcp-recv", bi_tcp_recv); DEF("tcp-send", bi_tcp_send); DEF("tcp-close", bi_tcp_close); + DEF("heap-snapshot", bi_heap_snapshot); + DEF("heap-restore", bi_heap_restore); + DEF("current-time-ms", bi_current_time_ms); DEF("open-input-string", bi_open_input_string); DEF("open-output-string", bi_open_output_string); DEF("get-output-string", bi_get_output_string); diff --git a/examples/http-client-bench.lsp b/examples/http-client-bench.lsp new file mode 100644 index 0000000..482d96a --- /dev/null +++ b/examples/http-client-bench.lsp @@ -0,0 +1,66 @@ +;;; http-client-bench.lsp — sequential HTTP load generator in Scheme +;;; +;;; Makes N requests to 127.0.0.1:PORT and reports elapsed wall time +;;; + requests/sec. Uses only the six tcp-* primitives, so it runs +;;; identically in Python, C, and asm. +;;; +;;; The earlier tests/web-benchmark.sh used curl — each curl fork+exec +;;; costs ~2 ms, swamping actual server work. This client keeps every +;;; request in-process: that irreducible cost disappears, so the real +;;; server throughput shows up. +;;; +;;; Usage: +;;; python3 uncommonlisp.py --fast examples/http-client-bench.lsp +;;; ./c/uncommonlisp examples/http-client-bench.lsp +;;; ./asm/uncommonlisp < examples/http-client-bench.lsp +;;; +;;; Override N or PORT by pre-setting *n-requests* / *port* before load. + +(define *host* "127.0.0.1") +(define *port* 8080) +(define *n-requests* 500) +(define *path* "/bench") + +(define *request* + (string-append + "GET " *path* " HTTP/1.0\r\n" + "Host: " *host* "\r\n" + "Connection: close\r\n\r\n")) + +;;; Pass snap as arg so heap-restore can rewind per-request +;;; allocations on asm without invalidating the client loop's +;;; closure env. + +(define (one-request) + (let ((sock (tcp-connect *host* *port*))) + (if sock + (begin + (tcp-send sock *request*) + (let ((resp (tcp-recv sock 8192))) + (tcp-close sock) + (if (and resp (> (string-length resp) 0)) 1 0))) + 0))) + +(define (client-loop n ok snap) + (if (= n 0) + ok + (let ((got (one-request))) + (heap-restore snap) + (client-loop (- n 1) (+ ok got) snap)))) + +(define t0 (current-time-ms)) +(define ok (client-loop *n-requests* 0 (heap-snapshot))) +(define t1 (current-time-ms)) + +(define elapsed-ms (- t1 t0)) +(define rps + (if (> elapsed-ms 0) + (quotient (* *n-requests* 1000) elapsed-ms) + 0)) + +(display "target : http://") (display *host*) (display ":") (display *port*) +(display *path*) (newline) +(display "requests : ") (display *n-requests*) (newline) +(display "ok : ") (display ok) (newline) +(display "elapsed : ") (display elapsed-ms) (display " ms") (newline) +(display "rps : ") (display rps) (newline) diff --git a/examples/http-server.lsp b/examples/http-server.lsp index eb160c2..bda5760 100644 --- a/examples/http-server.lsp +++ b/examples/http-server.lsp @@ -15,12 +15,17 @@ (define *crlf* "\r\n") (define *crlf-crlf* "\r\n\r\n") -;;; Hard request ceiling. The asm impl has no GC: every request -;;; bump-allocates heap cells that never get reclaimed. An unbounded -;;; server leaks ~64 MB per heap growth until OOM. This ceiling makes -;;; the server self-terminate long before it starves the machine. -;;; Benchmarks that want a different cap should `set!` it after load. -(define *max-requests* 50000) +;;; Request ceiling. Acts as a belt-and-suspenders cap under the +;;; heap-snapshot loop below. The snapshot mechanism (asm only) rewinds +;;; per-request allocations so memory stays O(1) regardless of ceiling. +;;; On Python + C the GC handles this; the cap still bounds accidentally +;;; runaway demo servers. +(define *max-requests* 1000000) + +;;; heap-snapshot / heap-restore are available in all three impls. +;;; On asm they rewind the bump allocator (recycling per-request +;;; allocations in O(1) memory). On Python + C they are no-ops +;;; because those runtimes already have a real GC. ;;; ── HTTP helpers ───────────────────────────────────────────── @@ -82,22 +87,31 @@ ;;; ── Main loop ─────────────────────────────────────────────── +;;; Top-level `server` and `server-loop`. The snapshot `snap` is +;;; passed down as an explicit arg — this avoids any issue with +;;; closures captured before/after the snapshot. + +(define server (tcp-listen *port*)) + +(define (server-loop n snap) + (if (>= n *max-requests*) + (begin + (display "request cap reached, exiting\n") + (tcp-close server)) + (begin + (let ((client (tcp-accept server))) + (let ((req (tcp-recv client 4096))) + (if (and req (> (string-length req) 0)) + (tcp-send client (handle-request req)) + #f)) + (tcp-close client)) + ;; Rewind per-request allocations on asm; no-op elsewhere. + (heap-restore snap) + (server-loop (+ n 1) snap)))) + (define (serve) - (let ((server (tcp-listen *port*))) - (display "uncommonlisp http server on :") (display *port*) - (display " (max ") (display *max-requests*) (display " requests)") (newline) - (let loop ((n 0)) - (if (>= n *max-requests*) - (begin - (display "request cap reached, exiting\n") - (tcp-close server)) - (begin - (let ((client (tcp-accept server))) - (let ((req (tcp-recv client 4096))) - (if (and req (> (string-length req) 0)) - (tcp-send client (handle-request req)) - #f)) - (tcp-close client)) - (loop (+ n 1))))))) + (display "uncommonlisp http server on :") (display *port*) + (display " (max ") (display *max-requests*) (display " requests)") (newline) + (server-loop 0 (heap-snapshot))) (serve) diff --git a/uncommonlisp.py b/uncommonlisp.py index bead283..c8a8ea5 100644 --- a/uncommonlisp.py +++ b/uncommonlisp.py @@ -322,12 +322,25 @@ def _tokenize(src): return [t for t in _TOK_RE.findall(src) if not t.startswith(';')] def _tokenize_lines(src): - """Tokenize with line numbers: returns list of (token, line_number) tuples.""" + """Tokenize with line numbers: returns list of (token, line_number) tuples. + + MOAD-0001 fix: precompute line-start offsets once (one pass over src), + then bisect_right to map any token position -> line in O(log M). + Total cost: O(M + N log M) instead of the old O(N*M) scan sediment. + """ + # line_starts[k] = byte offset where line (k+1) begins; line 1 starts at 0. + line_starts = [0] + i = src.find('\n') + while i != -1: + line_starts.append(i + 1) + i = src.find('\n', i + 1) + from bisect import bisect_right + result = [] for m in _TOK_RE.finditer(src): tok = m.group() if tok.startswith(';'): continue - line = src.count('\n', 0, m.start()) + 1 + line = bisect_right(line_starts, m.start()) result.append((tok, line)) return result @@ -3260,6 +3273,12 @@ def make_global_env(): d(S('tcp-recv'), lambda a, _: _tcp_recv(a[0], int(a[1]))) d(S('tcp-send'), lambda a, _: _tcp_send(a[0], _str_val(a[1]))) d(S('tcp-close'), lambda a, _: (a[0].close(), VOID)[-1]) + # heap-snapshot/heap-restore are asm-only arena primitives. Python has + # real GC so these are no-ops here — they exist only to let portable + # .lsp code call them unconditionally. + d(S('heap-snapshot'), lambda a, _: False) + d(S('heap-restore'), lambda a, _: VOID) + d(S('current-time-ms'), lambda a, _: int(__import__('time').time() * 1000)) d(S('open-input-string'), lambda a, _: StringInputPort(_str_val(a[0]))) d(S('open-output-string'),lambda a, _: StringOutputPort()) d(S('get-output-string'), lambda a, _: a[0].getvalue() if isinstance(a[0], StringOutputPort) else '')