/static/codemoomoo.png

OCaml 5 สำหรับการพัฒนาระบบ IT: Memory Safety, Concurrency และ Multicore

Part 5 — Practical IT Systems Development with OCaml 5

ส่วนที่ 5 นี้นำเสนอการประยุกต์ใช้ OCaml 5 กับงานระบบจริง ครอบคลุมการเขียนโปรแกรม I/O ระดับต่ำ การสื่อสารเครือข่าย การ serialize ข้อมูล การเชื่อมต่อฐานข้อมูล การสร้าง CLI tools และการปรับแต่งประสิทธิภาพ โดยเน้นตัวอย่างที่พร้อมนำไปใช้งานจริงในระบบ production


19. Systems I/O และ OS Interface

การเขียนโปรแกรมระบบ (systems programming) ต้องสามารถเข้าถึงทรัพยากรของระบบปฏิบัติการได้โดยตรง OCaml 5 มีโมดูล Unix เป็น standard library ที่ห่อหุ้ม (wrap) POSIX system calls ไว้ และเมื่อใช้ร่วมกับ Eio จะได้ประสิทธิภาพแบบ non-blocking asynchronous I/O ที่ทัดเทียมกับภาษา systems สมัยใหม่

19.1 Low-level I/O: Unix Module

โมดูล Unix ให้การเข้าถึง system calls (การเรียกระบบ) ที่สำคัญ เช่น การเปิดไฟล์ อ่าน/เขียน และจัดการ file descriptor (ตัวชี้ไฟล์ที่ kernel ใช้)

Primitive หลักที่ควรรู้:

(* file: low_level_io.ml *)
(* ตัวอย่างการอ่านไฟล์ระดับต่ำด้วย Unix module
   โดยใช้ buffer ขนาดคงที่และจัดการ fd แบบปลอดภัย *)

let read_file_lowlevel path =
  (* เปิดไฟล์แบบ read-only พร้อม permission 0o644 *)
  let fd = Unix.openfile path [ Unix.O_RDONLY ] 0o644 in
  let buffer = Bytes.create 4096 in
  let buf_out = Buffer.create 4096 in
  let rec loop () =
    (* อ่านเข้า buffer ทีละ chunk ขนาด 4 KiB *)
    let n = Unix.read fd buffer 0 (Bytes.length buffer) in
    if n > 0 then begin
      Buffer.add_subbytes buf_out buffer 0 n;
      loop ()
    end
  in
  (* ใช้ Fun.protect เพื่อให้แน่ใจว่า fd จะถูกปิดเสมอ
     แม้ว่า loop จะ raise exception ขึ้นมา *)
  Fun.protect
    ~finally:(fun () -> Unix.close fd)
    loop;
  Buffer.contents buf_out

(* ตัวอย่างการใช้งาน *)
let () =
  let content = read_file_lowlevel "/etc/hostname" in
  Printf.printf "Hostname: %s" content

สังเกตการใช้ Fun.protect ซึ่งเป็น pattern ที่ปลอดภัยกว่า try-finally แบบ manual และเป็น idiom มาตรฐานใน OCaml สำหรับ resource cleanup

19.2 File Descriptors, Sockets และ Pipes

File descriptor เป็น integer ที่ kernel ใช้อ้างถึงทรัพยากร I/O ทุกประเภท (ไฟล์ socket pipe terminal)

(* file: pipe_ipc.ml *)
(* Pipe สำหรับ Inter-Process Communication (IPC) ระหว่าง parent-child *)

let pipe_demo () =
  (* สร้าง pipe ได้ tuple (read_end, write_end) *)
  let read_fd, write_fd = Unix.pipe () in
  match Unix.fork () with
  | 0 ->
      (* Child process: ปิดฝั่งอ่าน เขียนข้อมูลลง write_fd *)
      Unix.close read_fd;
      let msg = "Hello from child\n" in
      let _ = Unix.write_substring write_fd msg 0 (String.length msg) in
      Unix.close write_fd;
      exit 0
  | pid ->
      (* Parent process: ปิดฝั่งเขียน อ่านจาก read_fd *)
      Unix.close write_fd;
      let buf = Bytes.create 1024 in
      let n = Unix.read read_fd buf 0 1024 in
      Unix.close read_fd;
      let _ = Unix.waitpid [] pid in
      Printf.printf "Parent received: %s" (Bytes.sub_string buf 0 n)

let () = pipe_demo ()

19.3 epoll/kqueue ผ่าน Eio Backends

Eio เป็น library ระดับสูงที่ห่อหุ้มกลไก event notification ของระบบปฏิบัติการต่าง ๆ ไว้ภายใต้ interface เดียวกัน

%%{init: {'theme':'base','themeVariables':{
  'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2',
  'primaryBorderColor':'#fabd2f','lineColor':'#83a598',
  'secondaryColor':'#504945','tertiaryColor':'#32302f',
  'background':'#282828','mainBkg':'#3c3836',
  'nodeBorder':'#fabd2f','clusterBkg':'#32302f',
  'clusterBorder':'#d65d0e','titleColor':'#fabd2f',
  'edgeLabelBackground':'#282828','textColor':'#ebdbb2'
}}}%%
flowchart TB
    subgraph App["OCaml Application
(โปรแกรมผู้ใช้)"] Code["Eio.Flow / Eio.Net API
(high-level API)"] end subgraph Eio["Eio Scheduler
(ตัวจัดคิว fiber)"] Fiber["Fiber Pool
(คิว coroutine)"] Sched["Event Loop
(วงรอเหตุการณ์)"] end subgraph Backends["Platform Backends"] Linux["eio_linux
io_uring / epoll"] BSD["eio_posix
kqueue (macOS/BSD)"] Win["eio_windows
IOCP"] end subgraph OS["Operating System Kernel"] Uring["io_uring"] Epoll["epoll"] Kqueue["kqueue"] Iocp["IOCP"] end Code --> Fiber Fiber --> Sched Sched --> Linux Sched --> BSD Sched --> Win Linux --> Uring Linux --> Epoll BSD --> Kqueue Win --> Iocp
(* file: eio_echo_server.ml *)
(* Echo server ใช้ Eio — non-blocking I/O พร้อม structured concurrency *)

let handle_client flow addr =
  Eio.traceln "New client: %a" Eio.Net.Sockaddr.pp addr;
  (* Eio.Flow.copy จะ loop อ่าน-เขียนจนกว่า client จะปิด *)
  try Eio.Flow.copy flow flow
  with End_of_file -> Eio.traceln "Client disconnected"

let run_server ~net ~sw port =
  let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, port) in
  let socket = Eio.Net.listen ~sw ~reuse_addr:true ~backlog:128 net addr in
  Eio.traceln "Listening on port %d" port;
  (* Loop รับ connection — ทุก fiber มี Switch เป็นเจ้าของ *)
  while true do
    Eio.Net.accept_fork ~sw socket
      ~on_error:(fun ex -> Eio.traceln "Error: %a" Fmt.exn ex)
      handle_client
  done

(* ตัวอย่างการใช้งาน: รัน server บน port 9000 *)
let () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run @@ fun sw ->
  run_server ~net:(Eio.Stdenv.net env) ~sw 9000

19.4 Signal Handling

Signal คือ software interrupt (การขัดจังหวะเชิงซอฟต์แวร์) ที่ OS ส่งมาให้ process เช่น SIGINT (Ctrl+C), SIGTERM, SIGHUP

(* file: signal_handling.ml *)
(* จัดการ signal อย่างปลอดภัย — graceful shutdown *)

let shutdown_requested = Atomic.make false

let install_handlers () =
  (* Sys.signal จะติดตั้ง handler — ต้องเป็น async-signal-safe
     ดังนั้นแค่ set flag แล้วให้ main loop ตรวจสอบเอง *)
  let handler _ = Atomic.set shutdown_requested true in
  Sys.set_signal Sys.sigint (Sys.Signal_handle handler);
  Sys.set_signal Sys.sigterm (Sys.Signal_handle handler)

let main_loop () =
  install_handlers ();
  let rec loop n =
    if Atomic.get shutdown_requested then begin
      Printf.printf "\nGraceful shutdown after %d iterations\n" n;
      (* ทำ cleanup: ปิด connection, flush log ฯลฯ *)
    end else begin
      Printf.printf "Working... %d\r%!" n;
      Unix.sleepf 0.5;
      loop (n + 1)
    end
  in
  loop 0

(* ตัวอย่างการใช้งาน: กด Ctrl+C เพื่อหยุด *)
let () = main_loop ()

19.5 Process Management: Fork, Exec, Waitpid

(* file: process_mgmt.ml *)
(* รันคำสั่งภายนอก แล้วเก็บ output — equivalent ของ subprocess.run() ใน Python *)

let run_command cmd args =
  (* สร้าง pipe สำหรับอ่าน stdout ของ child *)
  let read_fd, write_fd = Unix.pipe () in
  match Unix.fork () with
  | 0 ->
      (* Child: redirect stdout ไป pipe write end *)
      Unix.close read_fd;
      Unix.dup2 write_fd Unix.stdout;
      Unix.close write_fd;
      (* execvp จะแทนที่ process image ด้วย cmd *)
      Unix.execvp cmd (Array.of_list (cmd :: args))
  | pid ->
      (* Parent: อ่านจาก pipe, รอ child เสร็จ *)
      Unix.close write_fd;
      let buf = Buffer.create 4096 in
      let chunk = Bytes.create 4096 in
      let rec read_loop () =
        let n = Unix.read read_fd chunk 0 4096 in
        if n > 0 then (Buffer.add_subbytes buf chunk 0 n; read_loop ())
      in
      read_loop ();
      Unix.close read_fd;
      let _, status = Unix.waitpid [] pid in
      let exit_code = match status with
        | Unix.WEXITED c -> c
        | Unix.WSIGNALED _ | Unix.WSTOPPED _ -> -1
      in
      (exit_code, Buffer.contents buf)

(* ตัวอย่างการใช้งาน *)
let () =
  let code, output = run_command "uname" ["-a"] in
  Printf.printf "Exit code: %d\nOutput: %s" code output

ตารางเปรียบเทียบ System Call Wrappers:

Function POSIX equivalent Blocking? ใช้เมื่อไหร่
Unix.read read(2) Blocking I/O แบบ synchronous ง่าย ๆ
Unix.select select(2) Blocking until ready multiplex fd จำนวนน้อย
Eio.Flow.read io_uring/epoll Non-blocking (fiber suspend) high-performance servers
Unix.fork fork(2) N/A สร้าง child process
Unix.execvp execvp(3) Replaces process รันโปรแกรมภายนอก

20. Network Programming

การเขียนโปรแกรมเครือข่าย (network programming) ใน OCaml 5 มีตัวเลือกตั้งแต่ระดับต่ำด้วย Unix module ไปจนถึง high-level frameworks อย่าง Dream และ Cohttp

20.1 TCP/UDP Server ด้วย Unix Module (Low-level)

(* file: tcp_echo_lowlevel.ml *)
(* TCP echo server แบบ thread-per-connection ด้วย Unix module *)

let handle_client client_fd =
  let buf = Bytes.create 1024 in
  let rec loop () =
    let n = Unix.read client_fd buf 0 1024 in
    if n > 0 then begin
      (* echo ข้อมูลกลับไปยัง client *)
      let _ = Unix.write client_fd buf 0 n in
      loop ()
    end
  in
  (try loop () with _ -> ());
  Unix.close client_fd

let run_tcp_server port =
  let server_fd = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  Unix.setsockopt server_fd Unix.SO_REUSEADDR true;
  let addr = Unix.ADDR_INET (Unix.inet_addr_any, port) in
  Unix.bind server_fd addr;
  Unix.listen server_fd 128;
  Printf.printf "TCP server listening on port %d\n%!" port;
  while true do
    let client_fd, _ = Unix.accept server_fd in
    (* spawn thread ต่อ 1 connection — ใช้ Thread จาก threads.posix *)
    let _ = Thread.create handle_client client_fd in
    ()
  done

(* ตัวอย่างการใช้งาน *)
let () = run_tcp_server 8080

20.2 HTTP Server ด้วย Dream หรือ Cohttp + Eio

Dream เป็น web framework ทันสมัย ที่เขียนง่ายคล้าย Flask แต่ type-safe

(* file: dream_api.ml *)
(* REST API ง่าย ๆ ด้วย Dream *)

let users = Hashtbl.create 16
let next_id = ref 1

let add_user name =
  let id = !next_id in
  incr next_id;
  Hashtbl.add users id name;
  id

(* Handler สำหรับ POST /users *)
let create_user req =
  let%lwt body = Dream.body req in
  match Yojson.Safe.from_string body with
  | `Assoc [("name", `String name)] ->
      let id = add_user name in
      let resp = `Assoc [("id", `Int id); ("name", `String name)] in
      Dream.json (Yojson.Safe.to_string resp)
  | _ -> Dream.respond ~status:`Bad_Request "Invalid JSON"

(* Handler สำหรับ GET /users/:id *)
let get_user req =
  let id_str = Dream.param req "id" in
  match int_of_string_opt id_str with
  | None -> Dream.respond ~status:`Bad_Request "Invalid ID"
  | Some id ->
      (match Hashtbl.find_opt users id with
       | None -> Dream.respond ~status:`Not_Found "User not found"
       | Some name ->
           let resp = `Assoc [("id", `Int id); ("name", `String name)] in
           Dream.json (Yojson.Safe.to_string resp))

(* ตัวอย่างการใช้งาน: รัน server พร้อม routing *)
let () =
  Dream.run ~port:8080
  @@ Dream.logger
  @@ Dream.router [
    Dream.post "/users" create_user;
    Dream.get "/users/:id" get_user;
  ]

ทดสอบด้วย curl:

curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Moo"}'
# => {"id":1,"name":"Moo"}

curl http://localhost:8080/users/1
# => {"id":1,"name":"Moo"}

20.3 Protocol Implementation: Binary Parsing ด้วย Cstruct/Angstrom

การ parse binary protocol (เช่น DNS, TLS, custom RPC) ต้องการ library ที่ปลอดภัยและเร็ว

(* file: binary_proto.ml *)
(* Parse custom binary message format:
   | 4 bytes magic | 1 byte version | 2 bytes length | payload |
*)

open Angstrom

(* ชนิดข้อมูลของ message หลัง parse *)
type message = {
  magic: int32;
  version: int;
  payload: string;
}

let parse_message =
  let* magic = BE.any_int32 in       (* big-endian 32-bit *)
  let* version = any_uint8 in
  let* length = BE.any_uint16 in
  let* payload = take length in      (* อ่าน payload ตาม length *)
  return { magic; version; payload }

let serialize_message msg =
  let open Faraday in
  let buf = create 256 in
  BE.write_uint32 buf msg.magic;
  write_uint8 buf msg.version;
  BE.write_uint16 buf (String.length msg.payload);
  write_string buf msg.payload;
  serialize_to_string buf

(* ตัวอย่างการใช้งาน: encode แล้ว decode กลับ *)
let () =
  let original = { magic = 0xDEADBEEFl; version = 1; payload = "Hello" } in
  let bytes = serialize_message original in
  match parse_string ~consume:All parse_message bytes with
  | Ok m ->
      Printf.printf "magic=0x%lX version=%d payload=%S\n"
        m.magic m.version m.payload
  | Error e -> Printf.printf "Parse error: %s\n" e

20.4 TLS ด้วย tls Library (Pure OCaml)

Library tls implement TLS 1.2/1.3 ทั้งหมดด้วย OCaml ล้วน ไม่พึ่ง OpenSSL ทำให้ได้ memory safety ตั้งแต่ระดับโปรโตคอล

(* file: tls_client.ml *)
(* HTTPS client ง่าย ๆ ด้วย tls + Eio *)

let fetch_https ~env host path =
  Eio.Switch.run @@ fun sw ->
  let net = Eio.Stdenv.net env in
  (* resolve hostname ผ่าน Eio.Net.getaddrinfo_stream *)
  let addrs = Eio.Net.getaddrinfo_stream ~service:"443" net host in
  let addr = List.hd addrs in
  let sock = Eio.Net.connect ~sw net addr in
  (* สร้าง TLS config พร้อม authenticator (verify cert) *)
  let host_name = Domain_name.host_exn (Domain_name.of_string_exn host) in
  let authenticator = Ca_certs.authenticator () |> Result.get_ok in
  let cfg = Tls.Config.client ~authenticator () |> Result.get_ok in
  let tls_flow = Tls_eio.client_of_flow cfg ~host:host_name sock in
  (* ส่ง HTTP request แบบดิบ ๆ ผ่าน TLS flow *)
  let req = Printf.sprintf
    "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n" path host in
  Eio.Flow.copy_string req tls_flow;
  let buf = Buffer.create 4096 in
  Eio.Flow.(copy tls_flow (buffer_sink buf));
  Buffer.contents buf

(* ตัวอย่างการใช้งาน *)
let () =
  Eio_main.run @@ fun env ->
  let response = fetch_https ~env "example.com" "/" in
  print_endline (String.sub response 0 (min 200 (String.length response)))

20.5 gRPC และ Protocol Buffers (ocaml-protoc)

สำหรับ microservices ที่ใช้ gRPC library ocaml-protoc สามารถ generate OCaml code จาก .proto file ได้

ตัวอย่าง .proto file:

// file: user.proto
syntax = "proto3";

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

message GetUserRequest { int32 id = 1; }

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
}

Generate code:

ocaml-protoc --ocaml_out ./src user.proto
# => สร้าง user.ml และ user.mli ที่มี type User.user และ functions encode/decode

ตารางเปรียบเทียบ Network Libraries:

Library Layer Sync/Async เหมาะกับ
Unix Socket Blocking เรียนรู้พื้นฐาน, legacy
Eio.Net Socket Async (fiber) High-perf servers
Cohttp-eio HTTP/1.1 Async RESTful services
Dream HTTP + routing Async (Lwt/Eio) Full-stack web apps
H2 HTTP/2 Async gRPC, modern APIs
tls TLS 1.2/1.3 Pluggable Security-critical

21. Data Serialization และ Parsing

21.1 Angstrom: Parser Combinator

Parser combinator คือแนวคิดการประกอบ parser เล็ก ๆ หลายตัวเป็น parser ใหญ่ ผ่าน function composition — อ่านง่าย แก้ไขง่าย และ type-safe

(* file: csv_parser.ml *)
(* Parse CSV แบบง่าย: รองรับ quoted strings และ escape *)

open Angstrom

let is_quote = Char.equal '"'
let is_comma = Char.equal ','
let is_newline = Char.equal '\n'

(* Field ที่ไม่มี quote: อ่านจนเจอ , หรือ \n *)
let unquoted_field =
  take_while (fun c -> not (is_comma c) && not (is_newline c))

(* Field ที่มี quote: "..." โดย "" ภายในแทน " หนึ่งตัว *)
let quoted_field =
  char '"' *>
  (many (
    (char '"' *> char '"' *> return '"') <|>
    (not_char '"')
  ) >>| fun chars -> String.init (List.length chars) (List.nth chars)) <*
  char '"'

let field = quoted_field <|> unquoted_field

(* Row = field คั่นด้วย comma *)
let row = sep_by (char ',') field

(* CSV = row คั่นด้วย newline *)
let csv = sep_by (char '\n') row

(* ตัวอย่างการใช้งาน *)
let () =
  let input = {|name,age,city
"Moo, the Cat",3,"Bangkok"
Alice,30,London|} in
  match parse_string ~consume:All csv input with
  | Ok rows ->
      List.iter (fun row ->
        Printf.printf "[%s]\n" (String.concat " | " row)
      ) rows
  | Error e -> print_endline e

21.2 Faraday: Serialization Counterpart

Faraday เป็น library serialize ที่เป็นคู่ขนานของ Angstrom — เขียน binary/text output แบบมี buffered writer

(* file: faraday_http.ml *)
(* Serialize HTTP response แบบ efficient *)

open Faraday

let write_http_response ~status ~headers ~body =
  let buf = create 4096 in
  (* Status line *)
  write_string buf (Printf.sprintf "HTTP/1.1 %d OK\r\n" status);
  (* Headers *)
  List.iter (fun (k, v) ->
    write_string buf (Printf.sprintf "%s: %s\r\n" k v)
  ) headers;
  write_string buf (Printf.sprintf "Content-Length: %d\r\n" (String.length body));
  write_string buf "\r\n";
  (* Body *)
  write_string buf body;
  serialize_to_string buf

(* ตัวอย่างการใช้งาน *)
let () =
  let resp = write_http_response
    ~status:200
    ~headers:[("Content-Type", "application/json")]
    ~body:{|{"ok":true}|}
  in
  print_endline resp

21.3 Yojson และ ppx_yojson_conv: JSON

ppx_yojson_conv เป็น PPX extension ที่ generate JSON encoder/decoder อัตโนมัติจาก type definition

(* file: json_demo.ml *)
(* ต้องเพิ่ม preprocess (pps ppx_yojson_conv) ใน dune file *)

(* ประกาศ type พร้อม [@@deriving yojson] *)
type address = {
  street: string;
  city: string;
  zipcode: string;
} [@@deriving yojson]

type user = {
  id: int;
  name: string;
  email: string option;       (* option = nullable field *)
  addresses: address list;    (* list = JSON array *)
} [@@deriving yojson]

(* ตัวอย่างการใช้งาน: encode/decode round-trip *)
let () =
  let u = {
    id = 1;
    name = "Moo";
    email = Some "moo@rmutsv.ac.th";
    addresses = [
      { street = "1 Ratchadamnoen"; city = "Songkhla"; zipcode = "90000" }
    ]
  } in
  (* encode → JSON string *)
  let json_str = Yojson.Safe.pretty_to_string (user_to_yojson u) in
  print_endline json_str;
  (* decode ← JSON string *)
  match user_of_yojson (Yojson.Safe.from_string json_str) with
  | u2 -> Printf.printf "Decoded name: %s\n" u2.name

21.4 Cstruct: Type-safe Binary Buffer

Cstruct คล้าย zerocopy ของ Rust — ประกาศ layout ของ binary struct แล้ว library จะ generate accessor ที่ปลอดภัย

(* file: cstruct_demo.ml *)
(* ต้องเพิ่ม (preprocess (pps ppx_cstruct)) ใน dune *)

(* ประกาศ struct แบบ Ethernet header *)
[%%cstruct
type ethernet = {
  dst: uint8_t [@len 6];     (* destination MAC *)
  src: uint8_t [@len 6];     (* source MAC *)
  ethertype: uint16_t;       (* 0x0800 = IPv4 *)
} [@@big_endian]]

(* Accessor ที่ถูก generate: set_ethernet_ethertype, get_ethernet_ethertype,
   copy_ethernet_dst, sizeof_ethernet ฯลฯ *)

let parse_ethernet_frame (buf: Cstruct.t) =
  if Cstruct.length buf < sizeof_ethernet then
    Error "Frame too short"
  else
    let dst = copy_ethernet_dst buf in
    let src = copy_ethernet_src buf in
    let ethertype = get_ethernet_ethertype buf in
    Ok (dst, src, ethertype)

let mac_to_string mac =
  String.init 17 (fun i ->
    if i mod 3 = 2 then ':'
    else
      let byte_idx = i / 3 in
      let nibble = if i mod 3 = 0 then (Char.code mac.[byte_idx] lsr 4)
                                   else (Char.code mac.[byte_idx] land 0xf) in
      "0123456789abcdef".[nibble])

(* ตัวอย่างการใช้งาน *)
let () =
  let frame = Cstruct.create 14 in
  set_ethernet_ethertype frame 0x0800;
  (match parse_ethernet_frame frame with
   | Ok (dst, src, et) ->
       Printf.printf "dst=%s src=%s ethertype=0x%04x\n"
         (mac_to_string dst) (mac_to_string src) et
   | Error e -> print_endline e)

21.5 Zarith สำหรับ Big Integer

Zarith ห่อหุ้ม GMP library ให้ OCaml ใช้ได้ จำเป็นสำหรับงาน cryptography และ finance ที่ต้องการ precision สูงกว่า 63-bit int

สูตรพื้นฐานของ modular exponentiation (ใช้ใน RSA, Diffie-Hellman):

c = m e mod n

โดยที่ m คือ plaintext, e คือ public exponent, n คือ modulus (product ของ primes สองตัว) และ c คือ ciphertext

(* file: zarith_demo.ml *)
(* ตัวอย่าง RSA encryption แบบง่าย — เพื่อการศึกษาเท่านั้น ห้ามใช้จริง *)

(* Zarith type: Z.t = integer ขนาดไม่จำกัด *)
let rsa_encrypt ~m ~e ~n = Z.powm m e n

let rsa_decrypt ~c ~d ~n = Z.powm c d n

(* ตัวอย่างการใช้งานด้วย primes เล็ก ๆ *)
let () =
  let p = Z.of_int 61 in
  let q = Z.of_int 53 in
  let n = Z.mul p q in                       (* n = 3233 *)
  let phi = Z.mul (Z.pred p) (Z.pred q) in   (* (p-1)(q-1) = 3120 *)
  let e = Z.of_int 17 in
  let d = Z.invert e phi in                  (* modular inverse *)
  let m = Z.of_int 65 in                     (* plaintext = 'A' *)
  let c = rsa_encrypt ~m ~e ~n in
  let m' = rsa_decrypt ~c ~d ~n in
  Printf.printf "plaintext=%s ciphertext=%s decrypted=%s\n"
    (Z.to_string m) (Z.to_string c) (Z.to_string m')

22. Database และ Storage

22.1 Caqti: Unified Async DB Interface

Caqti เป็น DB library ที่ไม่ผูกกับ driver เดียว — เขียน query ครั้งเดียว ใช้ได้ทั้ง PostgreSQL, SQLite, MariaDB

(* file: caqti_demo.ml *)
(* CRUD ง่าย ๆ ด้วย Caqti + SQLite *)

open Caqti_request.Infix
open Caqti_type.Std

(* ประกาศ query พร้อม type ของ params และ result *)
let create_table =
  (unit ->. unit) {|
    CREATE TABLE IF NOT EXISTS users (
      id   INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      age  INTEGER NOT NULL
    )
  |}

let insert_user =
  (t2 string int ->. unit)
  "INSERT INTO users (name, age) VALUES (?, ?)"

let get_user_by_id =
  (int ->? t2 string int)
  "SELECT name, age FROM users WHERE id = ?"

let list_users =
  (unit ->* t3 int string int)
  "SELECT id, name, age FROM users ORDER BY id"

(* ทุก function ของ Caqti คืน Lwt.t หรือ Eio result
   ในที่นี้ใช้ Caqti_lwt สำหรับตัวอย่าง *)
let ( let* ) = Lwt_result.bind

let run (module Db : Caqti_lwt.CONNECTION) =
  let* () = Db.exec create_table () in
  let* () = Db.exec insert_user ("Moo", 30) in
  let* () = Db.exec insert_user ("Alice", 25) in
  let* users = Db.collect_list list_users () in
  List.iter (fun (id, name, age) ->
    Printf.printf "#%d %s (%d)\n" id name age
  ) users;
  Lwt_result.return ()

(* ตัวอย่างการใช้งาน *)
let () =
  let uri = Uri.of_string "sqlite3:demo.db" in
  Lwt_main.run @@
    let open Lwt.Syntax in
    let* conn_result = Caqti_lwt_unix.connect uri in
    match conn_result with
    | Error e -> Lwt.return (print_endline (Caqti_error.show e))
    | Ok conn ->
        let+ result = run conn in
        (match result with
         | Ok () -> print_endline "Done"
         | Error e -> print_endline (Caqti_error.show e))

22.2 Connection Pooling ด้วย Eio

สำหรับ web service ต้องใช้ connection pool เพื่อไม่ให้เปิด-ปิด connection ใหม่ทุก request

(* file: conn_pool.ml *)
(* Connection pool ง่าย ๆ ด้วย Eio.Stream และ Eio.Switch *)

module Pool = struct
  type 'a t = {
    stream: 'a Eio.Stream.t;
    create: unit -> 'a;
    destroy: 'a -> unit;
  }

  let create ~size ~create ~destroy () =
    let stream = Eio.Stream.create size in
    for _ = 1 to size do
      Eio.Stream.add stream (create ())
    done;
    { stream; create; destroy }

  (* ยืม connection ชั่วคราว แล้วคืนเมื่อเสร็จ *)
  let with_conn t f =
    let conn = Eio.Stream.take t.stream in
    Fun.protect
      ~finally:(fun () -> Eio.Stream.add t.stream conn)
      (fun () -> f conn)

  let close t =
    for _ = 1 to Eio.Stream.length t.stream do
      t.destroy (Eio.Stream.take t.stream)
    done
end

(* ตัวอย่างการใช้งานกับ mock DB connection *)
let () =
  Eio_main.run @@ fun _env ->
  Eio.Switch.run @@ fun sw ->
  let pool = Pool.create ~size:5
    ~create:(fun () ->
      Printf.printf "Creating connection\n";
      ref 0)
    ~destroy:(fun _ -> Printf.printf "Destroying\n")
    ()
  in
  (* Spawn 10 fiber ใช้ pool ขนาด 5 *)
  for i = 1 to 10 do
    Eio.Fiber.fork ~sw (fun () ->
      Pool.with_conn pool (fun conn ->
        incr conn;
        Printf.printf "Fiber %d: used connection %d times\n" i !conn))
  done;
  Eio.Switch.on_release sw (fun () -> Pool.close pool)

22.3 Index Library: Persistent Key-Value Store

Index เป็น append-only on-disk KV store ที่เร็วมาก เหมาะสำหรับ cache, log, หรือ inverted index

(* file: index_demo.ml *)
(* Persistent key-value store ด้วย Index *)

(* ต้องนิยาม Key และ Value modules ที่บอกขนาด hash encode/decode *)
module Key = struct
  type t = string
  let v x = x
  let hash = Hashtbl.hash
  let hash_size = 30
  let encode s = s
  let decode s off = String.sub s off 32
  let encoded_size = 32
  let equal = String.equal
  let pp = Format.pp_print_string
end

module Value = struct
  type t = int64
  let encode x =
    let buf = Bytes.create 8 in
    Bytes.set_int64_le buf 0 x;
    Bytes.unsafe_to_string buf
  let decode s off = Bytes.get_int64_le (Bytes.of_string s) off
  let encoded_size = 8
  let pp = Format.pp_print_int64
end

module I = Index_unix.Make (Key) (Value) (Index.Cache.Unbounded)

(* ตัวอย่างการใช้งาน *)
let () =
  let idx = I.v ~log_size:1000 "./my_index" in
  (* เขียนหลาย key-value *)
  let pad k = let s = k ^ String.make 32 '\x00' in String.sub s 0 32 in
  I.replace idx (pad "user:1") 100L;
  I.replace idx (pad "user:2") 200L;
  I.flush idx;
  (* อ่านกลับ *)
  Printf.printf "user:1 = %Ld\n" (I.find idx (pad "user:1"));
  I.close idx

22.4 Irmin: Git-like Distributed Store

Irmin คือ Mirage project library ที่ทำให้ข้อมูลทุกอย่างมี history แบบ Git — branch, merge, diff ได้

%%{init: {'theme':'base','themeVariables':{
  'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2',
  'primaryBorderColor':'#fabd2f','lineColor':'#83a598',
  'secondaryColor':'#504945','tertiaryColor':'#32302f',
  'background':'#282828','mainBkg':'#3c3836',
  'nodeBorder':'#fabd2f','clusterBkg':'#32302f',
  'clusterBorder':'#d65d0e','titleColor':'#fabd2f',
  'edgeLabelBackground':'#282828','textColor':'#ebdbb2'
}}}%%
gitGraph
  commit id: "init"
  commit id: "add user:1"
  branch feature
  checkout feature
  commit id: "experiment"
  commit id: "experiment v2"
  checkout main
  commit id: "add user:2"
  merge feature id: "merge feature"
  commit id: "release v1"
(* file: irmin_demo.ml *)
(* Irmin in-memory store with branching *)

module Store = Irmin_mem.KV.Make (Irmin.Contents.String)
module Info = Irmin_unix.Info (Store.Info)

let info msg = Info.v ~author:"moo" "%s" msg

(* ตัวอย่างการใช้งาน: commit, branch, merge *)
let main () =
  let open Lwt.Syntax in
  let config = Irmin_mem.config () in
  let* repo = Store.Repo.v config in
  let* main_branch = Store.main repo in
  (* Commit #1 *)
  let* () = Store.set_exn ~info:(info "add readme") main_branch
    ["readme"] "Hello Irmin" in
  (* สร้าง branch ใหม่ *)
  let* dev = Store.of_branch repo "dev" in
  let* () = Store.merge_into ~info:(info "sync") main_branch ~into:dev
    >|= Result.get_ok in
  let* () = Store.set_exn ~info:(info "add feature") dev
    ["feature"] "experimental" in
  (* Merge กลับไป main *)
  let* () = Store.merge_into ~info:(info "release") dev ~into:main_branch
    >|= Result.get_ok in
  let* content = Store.get main_branch ["feature"] in
  Lwt_io.printf "feature = %s\n" content

let () = Lwt_main.run (main ())

ตารางเปรียบเทียบ Storage Solutions:

Library ชนิด Concurrent writers Best for
Caqti SQL client ขึ้นกับ DB OLTP, รายงาน
Index KV store No (single-writer) Cache, log
Irmin Git-like Yes (via merge) Distributed config, state
lmdb Embedded KV Yes (MVCC) High-throughput read

23. CLI Tools และ System Utilities

23.1 Cmdliner: Declarative CLI Argument Parsing

Cmdliner มองทุก command-line option เป็น term ที่ประกอบกันแบบ applicative — auto generate man page ได้

(* file: cli_app.ml *)
(* CLI tool สำหรับ ping server พร้อม options *)

open Cmdliner

(* ประกาศ arguments เป็น term *)
let host =
  let doc = "Hostname หรือ IP address ของ server" in
  Arg.(required & pos 0 (some string) None & info [] ~docv:"HOST" ~doc)

let port =
  let doc = "TCP port" in
  Arg.(value & opt int 80 & info ["p"; "port"] ~docv:"PORT" ~doc)

let count =
  let doc = "จำนวนครั้งที่ ping" in
  Arg.(value & opt int 4 & info ["c"; "count"] ~docv:"N" ~doc)

let verbose =
  let doc = "แสดงรายละเอียดการทำงาน" in
  Arg.(value & flag & info ["v"; "verbose"] ~doc)

(* Function หลัก *)
let run host port count verbose =
  if verbose then Printf.printf "Pinging %s:%d %d times\n" host port count;
  for i = 1 to count do
    Printf.printf "Ping #%d to %s:%d\n" i host port
  done;
  0  (* exit code *)

(* ประกอบ term เข้าด้วยกัน *)
let cmd =
  let doc = "Ping a TCP server" in
  let info = Cmd.info "tcping" ~version:"1.0" ~doc in
  Cmd.v info Term.(const run $ host $ port $ count $ verbose)

(* ตัวอย่างการใช้งาน: dune exec ./cli_app.exe -- example.com -p 443 -c 3 -v *)
let () = exit (Cmd.eval' cmd)

23.2 Logs + Fmt: Structured Logging

(* file: logging_demo.ml *)
(* Structured logging ด้วย Logs + Fmt *)

(* ประกาศ log source สำหรับแต่ละ module *)
let src = Logs.Src.create "myapp.db" ~doc:"Database operations"
module Log = (val Logs.src_log src : Logs.LOG)

let process_user user_id =
  Log.debug (fun m -> m "Processing user %d" user_id);
  if user_id <= 0 then begin
    Log.err (fun m -> m "Invalid user_id: %d" user_id);
    Error "invalid"
  end else begin
    Log.info (fun m -> m "User %d processed successfully" user_id);
    Ok user_id
  end

(* ตัวอย่างการตั้งค่า log *)
let setup_log level =
  Logs.set_level (Some level);
  (* reporter ที่เขียนลง stderr พร้อมสีและ timestamp *)
  Logs.set_reporter (Logs_fmt.reporter ())

let () =
  setup_log Logs.Debug;
  let _ = process_user 42 in
  let _ = process_user (-1) in
  ()

23.3 Configuration: ไฟล์ config + Environment Variables

(* file: config.ml *)
(* Config แบบ layered: defaults < env vars < config file < CLI args *)

type config = {
  db_host: string;
  db_port: int;
  workers: int;
  log_level: string;
}

let default_config = {
  db_host = "localhost";
  db_port = 5432;
  workers = 4;
  log_level = "info";
}

let from_env cfg =
  let get key default =
    match Sys.getenv_opt key with Some v -> v | None -> default
  in
  let get_int key default =
    match Sys.getenv_opt key with
    | Some v -> (match int_of_string_opt v with Some i -> i | None -> default)
    | None -> default
  in
  { cfg with
    db_host = get "DB_HOST" cfg.db_host;
    db_port = get_int "DB_PORT" cfg.db_port;
    workers = get_int "WORKERS" cfg.workers;
    log_level = get "LOG_LEVEL" cfg.log_level;
  }

let from_toml cfg path =
  if not (Sys.file_exists path) then cfg
  else
    (* ในงานจริงใช้ library เช่น otoml ทำ parse *)
    let ic = open_in path in
    let _content = In_channel.input_all ic in
    close_in ic;
    cfg  (* placeholder: ใส่ logic parsing จริง *)

(* ตัวอย่างการใช้งาน *)
let load_config () =
  default_config
  |> fun c -> from_toml c "/etc/myapp/config.toml"
  |> from_env

let () =
  let cfg = load_config () in
  Printf.printf "DB: %s:%d, workers=%d, level=%s\n"
    cfg.db_host cfg.db_port cfg.workers cfg.log_level

23.4 Daemonization และ systemd Integration

สำหรับ modern Linux ไม่แนะนำ ให้ daemonize เองด้วย fork() สองครั้งแบบโบราณ — ให้ systemd จัดการแทน

# file: /etc/systemd/system/myapp.service
[Unit]
Description=My OCaml Application
After=network-online.target
Wants=network-online.target

[Service]
Type=notify                          # ใช้ sd_notify protocol
ExecStart=/usr/local/bin/myapp --port 8080
Restart=on-failure
RestartSec=5
User=myapp
Group=myapp
Environment=LOG_LEVEL=info
Environment=DB_HOST=localhost
# Resource limits
LimitNOFILE=65536
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
(* file: sd_notify.ml *)
(* แจ้ง systemd ว่า service พร้อมให้บริการแล้ว *)

let notify_systemd msg =
  match Sys.getenv_opt "NOTIFY_SOCKET" with
  | None -> ()  (* ไม่ได้รันภายใต้ systemd *)
  | Some sock_path ->
      let sock = Unix.socket Unix.PF_UNIX Unix.SOCK_DGRAM 0 in
      let addr = Unix.ADDR_UNIX sock_path in
      let _ = Unix.sendto_substring sock msg 0 (String.length msg) [] addr in
      Unix.close sock

(* ตัวอย่างการใช้งาน *)
let () =
  (* เริ่มต้น service ... *)
  Unix.sleep 1;
  notify_systemd "READY=1";
  notify_systemd "STATUS=Serving requests on port 8080";
  (* loop service ... *)
  while true do Unix.sleep 60 done

คำสั่ง systemd ที่ใช้บ่อย:

sudo systemctl daemon-reload          # โหลด unit file ใหม่
sudo systemctl enable --now myapp     # enable + start
sudo systemctl status myapp           # เช็คสถานะ
sudo journalctl -u myapp -f           # ดู log แบบ follow
sudo systemctl restart myapp          # restart service

24. Performance Optimization

การปรับแต่งประสิทธิภาพควรเริ่มจาก profile ก่อน optimize เสมอ — Donald Knuth กล่าวไว้ว่า premature optimization is the root of all evil

24.1 Profiling ด้วย perf + Landmarks

perf เป็น Linux profiler ที่อ่าน CPU performance counters โดยตรง — ต้อง compile OCaml ด้วย flag -g และ (ocamlopt_flags (:standard -g))

# Compile พร้อม debug symbol
dune build --profile=release

# Profile 10 วินาที
sudo perf record -F 99 -g -- ./_build/default/bin/myapp
sudo perf report

# Flame graph
sudo perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg

Landmarks เป็น OCaml-native profiler ที่ใช้ PPX annotation

(* file: landmarks_demo.ml *)
(* ต้องเพิ่ม (preprocess (pps landmarks-ppx --auto)) ใน dune file *)

(* [@landmark] บอกให้ wrap function ด้วย timing code *)
let heavy_computation n =
  [@landmark "heavy_computation"]
  let rec loop acc i =
    if i > n then acc
    else loop (acc +. sin (float_of_int i)) (i + 1)
  in
  loop 0.0 0

let process_batch items =
  [@landmark "process_batch"]
  List.map heavy_computation items

(* ตัวอย่างการใช้งาน *)
(* รันด้วย: OCAML_LANDMARKS=on ./app
   จะพิมพ์สถิติเวลาออกมาเมื่อโปรแกรมจบ *)
let () =
  let results = process_batch [1000; 2000; 3000; 4000] in
  List.iter (Printf.printf "%.4f\n") results

24.2 Inlining Hints และ Attributes

OCaml compiler สามารถ inline function ได้อัตโนมัติ แต่บางครั้งต้องบอกเป็นคำสั่ง

(* file: inline_hints.ml *)

(* [@inline always] บังคับ inline แน่นอน *)
let[@inline always] square x = x * x

(* [@inline never] ห้าม inline (เช่น function ใหญ่ หรือ cold path) *)
let[@inline never] rare_error_path msg = failwith msg

(* [@specialise] ให้ compiler สร้าง version ที่ specialize
   สำหรับ argument แบบ function *)
let rec map f lst =
  [@specialise]
  match lst with
  | [] -> []
  | x :: rest -> f x :: map f rest

(* [@unrolled n] unroll loop n ครั้ง *)
let sum_array arr =
  let n = Array.length arr in
  let acc = ref 0 in
  for i = 0 to n - 1 do
    [@unrolled 4]
    acc := !acc + arr.(i)
  done;
  !acc

(* ตัวอย่างการใช้งาน *)
let () =
  let arr = Array.init 1000 (fun i -> i) in
  Printf.printf "Sum = %d\n" (sum_array arr);
  Printf.printf "Square 7 = %d\n" (square 7)

24.3 Avoid Boxing: Float Array, Bigarray

ใน OCaml float ปกติจะถูก box (เก็บเป็น pointer ไปยัง heap cell) แต่ float array จะ unbox อัตโนมัติ ลด allocation มหาศาล

Memory overhead = sboxed - sunboxed sunboxed × 100 %

โดยที่ sboxed คือขนาดในหน่วย bytes เมื่อเก็บแบบ boxed (pointer + header + payload) และ sunboxed คือขนาดแบบ unboxed (payload อย่างเดียว)

(* file: unbox_demo.ml *)
(* เปรียบเทียบ performance ของ structure ที่ box vs unbox *)

(* BAD: tuple ของ float → box + pointer chasing *)
type point_boxed = float * float

let sum_boxed (points: point_boxed list) =
  List.fold_left (fun acc (x, y) -> acc +. x +. y) 0.0 points

(* GOOD: record with float-only fields → unboxed representation *)
type point_flat = { mutable x: float; mutable y: float }

(* GOOD: Bigarray สำหรับ numerical arrays ขนาดใหญ่ *)
module BA = Bigarray

let sum_bigarray (arr: (float, BA.float64_elt, BA.c_layout) BA.Array1.t) =
  let n = BA.Array1.dim arr in
  let acc = ref 0.0 in
  for i = 0 to n - 1 do
    acc := !acc +. BA.Array1.unsafe_get arr i
  done;
  !acc

(* ตัวอย่างการใช้งาน + benchmark *)
let () =
  let n = 1_000_000 in
  (* Bigarray: off-heap allocation, GC ไม่ต้องสนใจ *)
  let arr = BA.Array1.create BA.float64 BA.c_layout n in
  for i = 0 to n - 1 do
    BA.Array1.unsafe_set arr i (float_of_int i)
  done;
  let t0 = Unix.gettimeofday () in
  let s = sum_bigarray arr in
  let t1 = Unix.gettimeofday () in
  Printf.printf "Sum = %.0f in %.4f s\n" s (t1 -. t0)

24.4 SIMD ผ่าน C Stubs

OCaml 5 ยังไม่มี native SIMD — ต้องเขียน C stub เอง

// file: simd_stub.c
// SIMD sum ด้วย AVX2 สำหรับ float64 array
#include <immintrin.h>
#include <caml/mlvalues.h>
#include <caml/bigarray.h>

CAMLprim value simd_sum_f64(value v_arr) {
    double* arr = Caml_ba_data_val(v_arr);
    intnat n = Caml_ba_array_val(v_arr)->dim[0];
    __m256d acc = _mm256_setzero_pd();
    intnat i = 0;
    // ประมวลผลครั้งละ 4 elements
    for (; i + 4 <= n; i += 4) {
        __m256d v = _mm256_loadu_pd(arr + i);
        acc = _mm256_add_pd(acc, v);
    }
    // Horizontal sum
    double buf[4];
    _mm256_storeu_pd(buf, acc);
    double total = buf[0] + buf[1] + buf[2] + buf[3];
    // Tail
    for (; i < n; i++) total += arr[i];
    return caml_copy_double(total);
}
(* file: simd.ml *)
(* Binding สำหรับ C function ด้านบน *)

external simd_sum_f64:
  (float, Bigarray.float64_elt, Bigarray.c_layout) Bigarray.Array1.t -> float
  = "simd_sum_f64"

(* ตัวอย่างการใช้งาน *)
let () =
  let n = 1_000_000 in
  let arr = Bigarray.Array1.create Bigarray.float64 Bigarray.c_layout n in
  for i = 0 to n - 1 do
    arr.{i} <- float_of_int i
  done;
  Printf.printf "SIMD sum = %.0f\n" (simd_sum_f64 arr)

dune file:

(executable
 (name simd)
 (libraries bigarray)
 (foreign_stubs
  (language c)
  (names simd_stub)
  (flags -mavx2 -O3)))

24.5 Benchmarking Workflow: bechamel

bechamel เป็น micro-benchmark library ที่มี statistical analysis (ค่า mean, standard deviation, confidence interval) — ทำให้เชื่อถือได้มากกว่าแค่วัดเวลาครั้งเดียว

สูตร standard error of the mean ที่ bechamel ใช้:

SE = σ n

โดยที่ σ คือ sample standard deviation และ n คือจำนวนครั้งที่รันซ้ำ — ยิ่ง SE ต่ำ ผลลัพธ์ยิ่งน่าเชื่อถือ

(* file: bench_demo.ml *)
(* เปรียบเทียบ performance 3 วิธีรวม list *)

open Bechamel
open Toolkit

(* Method 1: List.fold_left (tail-recursive) *)
let fold_sum lst = List.fold_left (+) 0 lst

(* Method 2: recursive naive (ไม่ tail-recursive) *)
let rec naive_sum = function
  | [] -> 0
  | x :: rest -> x + naive_sum rest

(* Method 3: Array.fold_left หลังแปลง list → array *)
let array_sum lst = Array.fold_left (+) 0 (Array.of_list lst)

(* ประกาศ benchmark แต่ละตัว *)
let make_bench name f data =
  Test.make ~name (Staged.stage (fun () -> f data))

let tests =
  let data = List.init 10_000 (fun i -> i) in
  Test.make_grouped ~name:"sum" [
    make_bench "fold_left" fold_sum data;
    make_bench "naive" naive_sum data;
    make_bench "via array" array_sum data;
  ]

(* ตัวอย่างการใช้งาน *)
let benchmark () =
  let ols = Analyze.ols ~r_square:true ~bootstrap:0
    ~predictors:[| Measure.run |] in
  let instances = Instance.[ minor_allocated; major_allocated; monotonic_clock ] in
  let cfg = Benchmark.cfg ~quota:(Time.second 1.0) ~kde:(Some 1000) () in
  let raw = Benchmark.all cfg instances tests in
  let results = List.map (fun i ->
    Analyze.all ols i raw) instances
  in
  Analyze.merge ols instances results

let () =
  let results = benchmark () in
  let open Notty_unix in
  let window = Bechamel_notty.Unit.create () in
  List.iter (fun (_, result) ->
    output_image (Bechamel_notty.Multiple.image_of_ols_results
      ~rect:{Bechamel_notty.w = 80; h = 1} ~predictor:Measure.run window result)
  ) (Hashtbl.fold (fun k v acc -> (k,v)::acc) results [])

ตัวอย่าง output:

                   mean           std dev    r²    allocated
sum/fold_left      45.2 μs        ± 0.8 μs   0.99  0 words
sum/naive          52.3 μs        ± 2.1 μs   0.95  0 words
sum/via array     120.5 μs        ± 3.2 μs   0.98  30 005 words

Performance checklist สำหรับ production code:

Technique เมื่อไหร่ใช้ ผลลัพธ์ที่คาดหวัง
[@inline always] บน hot function สั้น ๆ Function ถูกเรียกใน loop ลด function call overhead 5–20%
Bigarray แทน float array ใหญ่ ๆ Numerical computation ลด GC pressure, off-heap
unsafe_get/unsafe_set หลัง verify bounds แล้ว ตัด bounds check 10–30%
Monomorphic compare compare int, float, bytes เร็วกว่า polymorphic = 2–5×
SIMD C stubs Math-heavy loops 2–4× speedup
Domainslib parallel_for Independent loop iterations scale ใกล้ #cores

สรุปท้ายส่วนที่ 5

บทนี้ครอบคลุมการนำ OCaml 5 ไปใช้กับงานระบบจริง ตั้งแต่ I/O ระดับต่ำกับ kernel การสร้าง network service การ serialize ข้อมูลด้วย parser combinator การเชื่อมต่อฐานข้อมูลแบบ type-safe การสร้าง CLI tool ที่เป็นมิตรต่อผู้ใช้ ไปจนถึงการวัดและปรับแต่งประสิทธิภาพด้วยวิธีการทางสถิติ

หลักการสำคัญที่ควรจำ:

  1. ใช้ Eio เป็น default สำหรับงาน I/O ใน OCaml 5 — ได้ทั้ง non-blocking, structured concurrency, และ portable backends
  2. แยก parsing จาก validation — ใช้ Angstrom/Cstruct parse ก่อน แล้วค่อย validate business rule ทีหลัง
  3. Profile ก่อน optimize — ใช้ landmarks/perf หา bottleneck จริง ไม่เดา
  4. Layer configuration — defaults < env < file < CLI ให้ยืดหยุ่นที่สุด
  5. ให้ systemd จัดการ lifecycle — อย่าเขียน daemonization logic เอง

ส่วนถัดไป (ส่วนที่ 6) จะลงลึกเรื่อง Patterns สำหรับ Production Systems รวมถึง supervision tree, resource management, testing และ deployment