Skip to content
Open
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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ HEMLOCKC_BIN="../hemlock/hemlockc" ./run.sh
- **array_sum** - Sum large array (memory, iteration)
- **string_concat** - String concatenation (allocation, GC pressure)
- **primes_sieve** - Sieve of Eratosthenes (array access, loops)
- **json_parse** - JSON parsing and serialization (string processing, object creation)
- **http_throughput** - HTTP server request throughput (real-world I/O, networking)

## Notes

Expand Down
17 changes: 17 additions & 0 deletions benchmarks.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@
"description": "Sieve of Eratosthenes",
"default_input": 1000000,
"quick_input": 100000
},
"json_parse": {
"description": "JSON parse and serialize",
"default_input": 100000,
"quick_input": 10000
},
"http_throughput": {
"description": "HTTP server request throughput",
"default_input": 2000,
"quick_input": 500,
"metric": "requests_per_second",
"higher_is_better": true
}
},
"languages": {
Expand Down Expand Up @@ -46,6 +58,11 @@
"extension": ".js",
"compile": null,
"run": "node {src} {input}"
},
"ruby": {
"extension": ".rb",
"compile": null,
"run": "ruby {src} {input}"
}
}
}
66 changes: 66 additions & 0 deletions benchmarks/http_throughput/http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
HTTP throughput benchmark client.
Makes N requests to a server and reports requests/second.
Uses one connection per request for consistent benchmarking.
"""

import sys
import time
import socket
import argparse

def make_requests(host, port, num_requests):
"""Make HTTP requests and return elapsed time in seconds."""
request = (
f"GET / HTTP/1.1\r\n"
f"Host: {host}:{port}\r\n"
f"Connection: close\r\n"
"\r\n"
).encode()

start = time.perf_counter()
completed = 0

for _ in range(num_requests):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
sock.connect((host, port))
sock.sendall(request)

# Read full response until connection closes
response = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk

sock.close()
completed += 1
except (socket.error, socket.timeout) as e:
# Skip failed requests
pass

elapsed = time.perf_counter() - start
return completed, elapsed

def main():
parser = argparse.ArgumentParser(description='HTTP throughput benchmark client')
parser.add_argument('--host', default='127.0.0.1', help='Server host')
parser.add_argument('--port', type=int, required=True, help='Server port')
parser.add_argument('--requests', type=int, default=10000, help='Number of requests')

args = parser.parse_args()

completed, elapsed = make_requests(args.host, args.port, args.requests)

if completed > 0:
rps = completed / elapsed
print(f"{rps:.2f}")
else:
print("0.00")

if __name__ == '__main__':
main()
100 changes: 100 additions & 0 deletions benchmarks/http_throughput/http_throughput.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// HTTP throughput benchmark - C implementation
// Simple HTTP server using raw sockets

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>

#define BUFFER_SIZE 4096

static const char *RESPONSE =
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Content-Length: 27\r\n"
"Connection: close\r\n"
"\r\n"
"{\"message\":\"Hello World!\"}";

static int server_fd = -1;
static volatile int running = 1;

void handle_signal(int sig) {
(void)sig;
running = 0;
if (server_fd != -1) {
close(server_fd);
}
}

int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <port>\n", argv[0]);
return 1;
}

int port = atoi(argv[1]);
struct sockaddr_in address;
int opt = 1;
char buffer[BUFFER_SIZE];

signal(SIGTERM, handle_signal);
signal(SIGINT, handle_signal);

if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
return 1;
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt");
return 1;
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
return 1;
}

if (listen(server_fd, 128) < 0) {
perror("listen");
return 1;
}

// Signal ready by printing port
printf("READY %d\n", port);
fflush(stdout);

size_t response_len = strlen(RESPONSE);

while (running) {
socklen_t addrlen = sizeof(address);
int client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen);
if (client_fd < 0) {
if (!running) break;
continue;
}

// Read request
ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
// Simple check for HTTP request
if (strncmp(buffer, "GET ", 4) == 0) {
write(client_fd, RESPONSE, response_len);
}
}

close(client_fd);
}

close(server_fd);
return 0;
}
36 changes: 36 additions & 0 deletions benchmarks/http_throughput/http_throughput.hml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// HTTP throughput benchmark - Hemlock implementation
// Uses Hemlock's built-in HTTP server support

fn main() {
let port_str = args[1];
let port = parse_int(port_str);

// Create HTTP server
let server = http_server(port);

// Register route handler
server.on("GET", "/", fn(req, res) {
res.json({ message: "Hello World!" });
});

// Signal ready
print("READY " + port_str);

// Start server (blocks)
server.listen();
}

fn parse_int(s) {
let result = 0;
let i = 0;
while (i < s.length) {
let ch = s[i];
let code: i32 = ch;
let digit = code - 48;
result = result * 10 + digit;
i = i + 1;
}
return result;
}

main();
41 changes: 41 additions & 0 deletions benchmarks/http_throughput/http_throughput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node
// HTTP throughput benchmark - JavaScript (Node.js) implementation

const http = require('http');

const port = parseInt(process.argv[2]);

if (!port) {
console.error('Usage: node http_throughput.js <port>');
process.exit(1);
}

const response = JSON.stringify({ message: 'Hello World!' });
const headers = {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(response),
'Connection': 'close'
};

const server = http.createServer((req, res) => {
if (req.method === 'GET') {
res.writeHead(200, headers);
res.end(response);
} else {
res.writeHead(405);
res.end();
}
});

server.listen(port, () => {
// Signal ready
console.log(`READY ${port}`);
});

process.on('SIGTERM', () => {
server.close(() => process.exit(0));
});

process.on('SIGINT', () => {
server.close(() => process.exit(0));
});
43 changes: 43 additions & 0 deletions benchmarks/http_throughput/http_throughput.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
# HTTP throughput benchmark - Python implementation

import sys
import signal
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class BenchmarkHandler(BaseHTTPRequestHandler):
# Disable logging for performance
def log_message(self, format, *args):
pass

def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Connection', 'close')
response = b'{"message":"Hello World!"}'
self.send_header('Content-Length', len(response))
self.end_headers()
self.wfile.write(response)

def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <port>", file=sys.stderr)
sys.exit(1)

port = int(sys.argv[1])
server = HTTPServer(('', port), BenchmarkHandler)

def shutdown(signum, frame):
server.shutdown()

signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)

# Signal ready
print(f"READY {port}", flush=True)

server.serve_forever()

if __name__ == '__main__':
main()
58 changes: 58 additions & 0 deletions benchmarks/http_throughput/http_throughput.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env ruby
# HTTP throughput benchmark - Ruby implementation

require 'socket'

port = ARGV[0]&.to_i

if port.nil? || port == 0
STDERR.puts "Usage: ruby http_throughput.rb <port>"
exit 1
end

response_body = '{"message":"Hello World!"}'
response = "HTTP/1.1 200 OK\r\n" \
"Content-Type: application/json\r\n" \
"Content-Length: #{response_body.length}\r\n" \
"Connection: close\r\n" \
"\r\n" \
"#{response_body}"

server = TCPServer.new('0.0.0.0', port)
server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)

running = true
trap('TERM') { running = false; server.close rescue nil }
trap('INT') { running = false; server.close rescue nil }

# Signal ready
puts "READY #{port}"
STDOUT.flush

while running
begin
client = server.accept

# Read request line
request = client.gets
next if request.nil?

# Read remaining headers
while (line = client.gets) && line != "\r\n"
# Skip headers
end

if request.start_with?('GET ')
client.write(response)
end

client.close rescue nil
rescue IOError, Errno::EBADF
break
rescue => e
# Connection errors are expected during shutdown
break unless running
end
end

server.close rescue nil
Loading