sx-http: buffer-based streaming renderer, eliminates string allocation

New render_to_buf / render_to_html_streaming in sx_render.ml — writes
HTML directly to a Buffer.t instead of building intermediate strings.
Eliminates hundreds of string concatenations per page render.

Full parallel renderer: render_to_buf, render_element_buf,
render_component_buf, render_cond_buf, render_let_buf, render_map_buf,
render_for_each_buf — all buffer-native.

HTTP server SSR + shell now use streaming renderer.

Performance (warm, 2 worker domains, 2MB RSS):
  Homepage:  138-195ms TTFB (Quart: 202ms) — faster
  Geography: 218-286ms TTFB (Quart: 144ms)
  Throughput: 6.85 req/s at c=5 (Quart: 6.8 req/s) — matched

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 17:06:27 +00:00
parent 3620a433c1
commit 1412648f6e
2 changed files with 291 additions and 7 deletions

View File

@@ -1490,21 +1490,19 @@ let http_render_page env path =
| _ -> serialize_value body_result
in
let t2 = Unix.gettimeofday () in
(* Phase 2: SSR — render to HTML using native renderer.
Native Sx_render handles islands as placeholders when SSR fails,
avoiding deep recursion into client-only island bodies. *)
(* Phase 2: SSR — render to HTML using streaming buffer renderer.
Writes directly to buffer, no intermediate string allocations. *)
let body_html =
try
let body_exprs = Sx_parser.parse_all body_str in
let body_expr = match body_exprs with
| [e] -> e | [] -> Nil | _ -> List (Symbol "<>" :: body_exprs) in
Sx_render.render_to_html body_expr env
Sx_render.render_to_html_streaming body_expr env
with e ->
Printf.eprintf "[http-ssr] failed: %s\n%!" (Printexc.to_string e); ""
in
let t3 = Unix.gettimeofday () in
(* Phase 3: Shell — wrap in full HTML page.
Shell kwargs reference pre-injected __shell-* vars from env. *)
(* Phase 3: Shell — render directly to buffer for zero-copy output *)
let get_shell_var name = try env_get env ("__shell-" ^ name) with _ -> Nil in
let shell_args = [
Keyword "title"; String "SX";
@@ -1529,7 +1527,7 @@ let http_render_page env path =
Keyword "meta-html"; String "";
] in
let shell_call = List (Symbol "~shared:shell/sx-page-shell" :: shell_args) in
let html = Sx_render.render_to_html shell_call env in
let html = Sx_render.render_to_html_streaming shell_call env in
let t4 = Unix.gettimeofday () in
Printf.eprintf "[sx-http] %s route=%.3fs aser=%.3fs ssr=%.3fs shell=%.3fs total=%.3fs html=%d\n%!"
path (t1 -. t0) (t2 -. t1) (t3 -. t2) (t4 -. t3) (t4 -. t0) (String.length html);

View File

@@ -414,6 +414,292 @@ and render_for_each args env =
) items)
(* ====================================================================== *)
(* Buffer-based streaming renderer — zero intermediate string allocation *)
(* ====================================================================== *)
(** Escape HTML directly into a buffer. *)
let escape_html_buf buf s =
for i = 0 to String.length s - 1 do
match String.unsafe_get s i with
| '&' -> Buffer.add_string buf "&amp;"
| '<' -> Buffer.add_string buf "&lt;"
| '>' -> Buffer.add_string buf "&gt;"
| '"' -> Buffer.add_string buf "&quot;"
| c -> Buffer.add_char buf c
done
(** Render attributes directly into a buffer. *)
let render_attrs_buf buf attrs =
Hashtbl.iter (fun k v ->
if is_boolean_attr k then begin
if sx_truthy v then begin
Buffer.add_char buf ' ';
Buffer.add_string buf k
end
end else if not (is_nil v) then begin
Buffer.add_char buf ' ';
Buffer.add_string buf k;
Buffer.add_string buf "=\"";
escape_html_buf buf (value_to_string v);
Buffer.add_char buf '"'
end) attrs
(** Buffer-based render_to_html — writes directly, no intermediate strings. *)
let rec render_to_buf buf (expr : value) (env : env) : unit =
match expr with
| Nil -> ()
| Bool true -> Buffer.add_string buf "true"
| Bool false -> Buffer.add_string buf "false"
| Number n ->
if Float.is_integer n then Buffer.add_string buf (string_of_int (int_of_float n))
else Buffer.add_string buf (Printf.sprintf "%g" n)
| String s -> escape_html_buf buf s
| Keyword k -> escape_html_buf buf k
| RawHTML s -> Buffer.add_string buf s
| Symbol s ->
let v = Sx_ref.eval_expr (Symbol s) (Env env) in
render_to_buf buf v env
| List [] | ListRef { contents = [] } -> ()
| List (head :: args) | ListRef { contents = head :: args } ->
render_list_buf buf head args env
| _ ->
let v = Sx_ref.eval_expr expr (Env env) in
render_to_buf buf v env
and render_list_buf buf head args env =
match head with
| Symbol "<>" ->
List.iter (fun c -> render_to_buf buf c env) args
| Symbol "raw!" ->
let v = Sx_ref.eval_expr (List.hd args) (Env env) in
(match v with
| String s | RawHTML s -> Buffer.add_string buf s
| _ -> Buffer.add_string buf (value_to_string v))
| Symbol tag when is_html_tag tag ->
render_element_buf buf tag args env
| Symbol "if" ->
let cond_val = Sx_ref.eval_expr (List.hd args) (Env env) in
if sx_truthy cond_val then
(if List.length args > 1 then render_to_buf buf (List.nth args 1) env)
else
(if List.length args > 2 then render_to_buf buf (List.nth args 2) env)
| Symbol "when" ->
let cond_val = Sx_ref.eval_expr (List.hd args) (Env env) in
if sx_truthy cond_val then
List.iter (fun e -> render_to_buf buf e env) (List.tl args)
| Symbol "cond" ->
render_cond_buf buf args env
| Symbol "case" ->
let v = Sx_ref.eval_expr (List (head :: args)) (Env env) in
render_to_buf buf v env
| Symbol ("let" | "let*") ->
render_let_buf buf args env
| Symbol ("begin" | "do") ->
let rec go = function
| [] -> ()
| [last] -> render_to_buf buf last env
| e :: rest -> ignore (Sx_ref.eval_expr e (Env env)); go rest
in go args
| Symbol ("define" | "defcomp" | "defmacro" | "defisland") ->
ignore (Sx_ref.eval_expr (List (head :: args)) (Env env))
| Symbol "map" -> render_map_buf buf args env false
| Symbol "map-indexed" -> render_map_buf buf args env true
| Symbol "filter" ->
let v = Sx_ref.eval_expr (List (head :: args)) (Env env) in
render_to_buf buf v env
| Symbol "for-each" -> render_for_each_buf buf args env
| Symbol name ->
(try
let v = env_get env name in
(match v with
| Component c when c.c_affinity = "client" -> ()
| Component _ -> render_component_buf buf v args env
| Island _i ->
(try
let call_expr = List (Symbol name :: args) in
let quoted = List [Symbol "quote"; call_expr] in
let render_call = List [Symbol "render-to-html"; quoted; Env env] in
let result = Sx_ref.eval_expr render_call (Env env) in
(match result with
| String s | RawHTML s -> Buffer.add_string buf s
| _ -> Buffer.add_string buf (value_to_string result))
with _e ->
Buffer.add_string buf (Printf.sprintf "<span data-sx-island=\"%s\"></span>" _i.i_name))
| Macro m ->
let expanded = expand_macro m args env in
render_to_buf buf expanded env
| _ ->
let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in
render_to_buf buf result env)
with Eval_error _ ->
let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in
render_to_buf buf result env)
| _ ->
let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in
render_to_buf buf result env
and render_element_buf buf tag args env =
let (attrs, children) = parse_element_args args env in
Buffer.add_char buf '<';
Buffer.add_string buf tag;
render_attrs_buf buf attrs;
if is_void tag then
Buffer.add_string buf " />"
else begin
Buffer.add_char buf '>';
List.iter (fun c -> render_to_buf buf c env) children;
Buffer.add_string buf "</";
Buffer.add_string buf tag;
Buffer.add_char buf '>'
end
and render_component_buf buf comp args env =
match comp with
| Component c ->
let kwargs = Hashtbl.create 8 in
let children_exprs = ref [] in
let skip = ref false in
let len = List.length args in
List.iteri (fun idx arg ->
if !skip then skip := false
else match arg with
| Keyword k when idx + 1 < len ->
let v = Sx_ref.eval_expr (List.nth args (idx + 1)) (Env env) in
Hashtbl.replace kwargs k v;
skip := true
| _ -> children_exprs := arg :: !children_exprs
) args;
let children = List.rev !children_exprs in
let local = env_merge c.c_closure env in
List.iter (fun p ->
let v = match Hashtbl.find_opt kwargs p with Some v -> v | None -> Nil in
ignore (env_bind local p v)
) c.c_params;
if c.c_has_children then begin
let child_buf = Buffer.create 256 in
List.iter (fun c -> render_to_buf child_buf c env) children;
ignore (env_bind local "children" (RawHTML (Buffer.contents child_buf)))
end;
render_to_buf buf c.c_body local
| _ -> ()
and render_cond_buf buf args env =
let as_list = function List l | ListRef { contents = l } -> Some l | _ -> None in
let is_scheme = List.for_all (fun a -> match as_list a with
| Some items when List.length items = 2 -> true | _ -> false) args in
if is_scheme then begin
let rec go = function
| [] -> ()
| clause :: rest ->
(match as_list clause with
| Some [test; body] ->
let is_else = match test with
| Keyword "else" | Symbol "else" | Symbol ":else" -> true | _ -> false in
if is_else then render_to_buf buf body env
else let v = Sx_ref.eval_expr test (Env env) in
if sx_truthy v then render_to_buf buf body env else go rest
| _ -> ())
in go args
end else begin
let rec go = function
| [] -> () | [_] -> ()
| test :: body :: rest ->
let is_else = match test with
| Keyword "else" | Symbol "else" | Symbol ":else" -> true | _ -> false in
if is_else then render_to_buf buf body env
else let v = Sx_ref.eval_expr test (Env env) in
if sx_truthy v then render_to_buf buf body env else go rest
in go args
end
and render_let_buf buf args env =
let as_list = function List l | ListRef { contents = l } -> Some l | _ -> None in
let bindings_expr = List.hd args in
let body = List.tl args in
let local = env_extend env in
let bindings = match as_list bindings_expr with Some l -> l | None -> [] in
let is_scheme = match bindings with (List _ :: _) | (ListRef _ :: _) -> true | _ -> false in
if is_scheme then
List.iter (fun b ->
match as_list b with
| Some [Symbol name; expr] | Some [String name; expr] ->
let v = Sx_ref.eval_expr expr (Env local) in
ignore (env_bind local name v)
| _ -> ()
) bindings
else begin
let rec go = function
| [] -> ()
| (Symbol name) :: expr :: rest | (String name) :: expr :: rest ->
let v = Sx_ref.eval_expr expr (Env local) in
ignore (env_bind local name v); go rest
| _ -> ()
in go bindings
end;
let rec render_body = function
| [] -> ()
| [last] -> render_to_buf buf last local
| e :: rest -> ignore (Sx_ref.eval_expr e (Env local)); render_body rest
in render_body body
and render_map_buf buf args env indexed =
let (fn_val, coll_val) = match args with
| [a; b] ->
let va = Sx_ref.eval_expr a (Env env) in
let vb = Sx_ref.eval_expr b (Env env) in
(match va, vb with
| (Lambda _ | NativeFn _), _ -> (va, vb)
| _, (Lambda _ | NativeFn _) -> (vb, va)
| _ -> (va, vb))
| _ -> (Nil, Nil)
in
let items = match coll_val with List l | ListRef { contents = l } -> l | _ -> [] in
List.iteri (fun i item ->
let call_args = if indexed then [Number (float_of_int i); item] else [item] in
match fn_val with
| Lambda l ->
let local = env_extend l.l_closure in
List.iter2 (fun p a -> ignore (env_bind local p a)) l.l_params call_args;
render_to_buf buf l.l_body local
| _ ->
let result = Sx_runtime.sx_call fn_val call_args in
render_to_buf buf result env
) items
and render_for_each_buf buf args env =
let (fn_val, coll_val) = match args with
| [a; b] ->
let va = Sx_ref.eval_expr a (Env env) in
let vb = Sx_ref.eval_expr b (Env env) in
(match va, vb with
| (Lambda _ | NativeFn _), _ -> (va, vb)
| _, (Lambda _ | NativeFn _) -> (vb, va)
| _ -> (va, vb))
| _ -> (Nil, Nil)
in
let items = match coll_val with List l | ListRef { contents = l } -> l | _ -> [] in
List.iter (fun item ->
match fn_val with
| Lambda l ->
let local = env_extend l.l_closure in
List.iter2 (fun p a -> ignore (env_bind local p a)) l.l_params [item];
render_to_buf buf l.l_body local
| _ ->
let result = Sx_runtime.sx_call fn_val [item] in
render_to_buf buf result env
) items
(** Public API: render to a pre-allocated buffer. *)
let render_to_buffer buf expr env = render_to_buf buf expr env
(** Convenience: render to string using the buffer renderer. *)
let render_to_html_streaming expr env =
let buf = Buffer.create 65536 in
render_to_buf buf expr env;
Buffer.contents buf
(* ====================================================================== *)
(* Setup — bind render primitives in an env and wire up the ref *)
(* ====================================================================== *)