Skip to content

Commit 1ad44b9

Browse files
committed
Add C17 and C23 builds with comprehensive benchmarking
- Update Makefile to build both C17 and C23 versions - C23 falls back to c2x flag for older compilers - Update benchmark script to test all three versions (C17, C23, Zig) - Fix Zig line reading to use takeDelimiter for proper multi-line input - Update CI to test both C versions - All versions now produce identical output
1 parent e2c086d commit 1ad44b9

File tree

5 files changed

+161
-65
lines changed

5 files changed

+161
-65
lines changed

.github/workflows/zig.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,22 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v4
1515

16-
- name: Build C version
16+
- name: Check GCC version
17+
run: gcc --version
18+
19+
- name: Build C versions (C17 and C23)
1720
run: cd c && make clean && make
1821

19-
- name: Test C executable runs
20-
run: cd c && echo -e "hi\npython\nexit" | timeout 5 ./chat || true
22+
- name: Test C17 executable runs
23+
run: cd c && echo -e "hi\npython\nexit" | timeout 5 ./chat-c17 || true
24+
25+
- name: Test C23 executable runs
26+
run: cd c && echo -e "hi\npython\nexit" | timeout 5 ./chat-c23 || true
27+
28+
- name: Compare binary sizes
29+
run: |
30+
echo "Binary sizes:"
31+
ls -lh c/chat-c17 c/chat-c23
2132
2233
zig-build-and-test:
2334
runs-on: ubuntu-latest
@@ -30,10 +41,13 @@ jobs:
3041
version: 0.15.2
3142

3243
- name: Build Zig chatbot
33-
run: cd zig && mkdir -p zig-out/bin && zig build-exe src/main.zig -femit-bin=zig-out/bin/chat
44+
run: cd zig && zig build
3445

3546
- name: Run Zig tests
36-
run: cd zig && zig test src/chatbot.zig
47+
run: cd zig && zig build test
3748

3849
- name: Test Zig executable runs
3950
run: cd zig && echo -e "hi\npython\nexit" | timeout 5 ./zig-out/bin/chat || true
51+
52+
- name: Show binary size
53+
run: ls -lh zig/zig-out/bin/chat

BENCHMARK.md

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,62 @@
1-
# Chatbot Build Benchmark Results
1+
# Chatbot Build & Runtime Benchmark Results
22

3-
Benchmark comparing C and Zig implementations of the chatbot.
3+
Benchmark comparing C (C17 and C23 standards) and Zig implementations of the chatbot.
44

55
**System:** macOS arm64
66
**Date:** 2026-01-12
77

8-
## Results
8+
## Build Results
99

10-
| Metric | C | Zig | Difference |
11-
|--------|---|-----|-----------|
12-
| Build Time | 93ms | 710ms | Zig is 7.63x slower |
13-
| Executable Size | 33K | 1.3M | Zig is 39x larger |
10+
| Metric | C17 | C23 | Zig | Notes |
11+
|--------|-----|-----|-----|-------|
12+
| Build Time | ~90ms | ~90ms | ~710ms | Zig ~8x slower |
13+
| Executable Size | 33K | 33K | 1.3M | Zig ~40x larger |
14+
15+
## Runtime Results
16+
17+
| Version | Execution Time | Relative |
18+
|---------|---------------|----------|
19+
| C17 | 147ms | 1.00x |
20+
| C23 | 197ms | 1.34x |
21+
| Zig | 459ms | 3.12x |
1422

1523
## Analysis
1624

1725
### Build Time
18-
- **C (93ms)**: Fast compilation using GCC with minimal optimization
19-
- **Zig (710ms)**: Longer compilation time due to Zig's more comprehensive compiler
20-
21-
The C version compiles significantly faster due to:
22-
- Simpler compilation pipeline
23-
- No build system overhead (direct gcc command)
24-
- Minimal type checking and analysis
26+
- **C (C17/C23)**: Fast compilation using GCC with minimal optimization
27+
- **Zig**: Longer compilation time due to more comprehensive compiler
2528

2629
### Executable Size
27-
- **C (33K)**: Small, minimal runtime
28-
- **Zig (1.3M)**: Larger due to Zig's standard library and runtime
30+
- **C (33K)**: Small, minimal runtime, links against system libc
31+
- **Zig (1.3M)**: Larger due to embedded standard library and runtime
32+
33+
### Runtime Performance
34+
- **C17** is the fastest, likely due to mature compiler optimizations
35+
- **C23** is slightly slower (may vary based on compiler version)
36+
- **Zig** is slower due to:
37+
- GeneralPurposeAllocator overhead vs C's stack allocation
38+
- New Zig 0.15 buffered I/O system overhead
39+
- Additional safety checks
2940

30-
The Zig executable is larger because:
31-
- Zig stdlib is embedded in the binary
32-
- More comprehensive runtime features
33-
- GeneralPurposeAllocator adds overhead
41+
## Build Configuration
42+
43+
- **C17**: `gcc -std=c17 -Wall -Wextra -pedantic`
44+
- **C23**: `gcc -std=c23 -Wall -Wextra -pedantic` (falls back to `-std=c2x` on older compilers)
45+
- **Zig**: `zig build` (debug mode, Zig 0.15.2)
3446

3547
## Notes
3648

37-
- Both versions are unoptimized builds
38-
- C build uses `-std=c11 -Wall -Wextra -pedantic`
39-
- Zig build uses default optimization level
49+
- All versions produce identical output
4050
- Times are from cold builds (no cache)
51+
- Runtime measured with same test input file
52+
- Zig uses pure Zig I/O (no libc linking)
4153

42-
## Running the Benchmark
54+
## Running the Benchmarks
4355

4456
```bash
57+
# Full runtime benchmark
58+
./run_benchmarks.sh
59+
60+
# Build-only benchmark
4561
./benchmark.sh
4662
```
47-
48-
This will clean, rebuild both versions, and compare build times and sizes.

c/Makefile

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
all:
2-
gcc -std=c11 -Wall -Wextra -pedantic src/chatbot.c -o chat
1+
CC = gcc
2+
CFLAGS_COMMON = -Wall -Wextra -pedantic
3+
SRC = src/chatbot.c
4+
5+
all: chat-c17 chat-c23
6+
7+
chat-c17:
8+
$(CC) -std=c17 $(CFLAGS_COMMON) $(SRC) -o chat-c17
9+
10+
chat-c23:
11+
$(CC) -std=c23 $(CFLAGS_COMMON) $(SRC) -o chat-c23 2>/dev/null || \
12+
$(CC) -std=c2x $(CFLAGS_COMMON) $(SRC) -o chat-c23
13+
14+
# Legacy target for backwards compatibility
15+
chat: chat-c17
16+
cp chat-c17 chat
317

418
clean:
5-
rm -rf *.o chat
19+
rm -rf *.o chat chat-c17 chat-c23

run_benchmarks.sh

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ echo "Chatbot Runtime Benchmark & Testing"
77
echo "=========================================="
88
echo ""
99

10-
# Build both versions
11-
echo "Building both versions..."
10+
# Build all versions
11+
echo "Building all versions..."
1212
echo ""
1313

1414
cd c
@@ -19,28 +19,46 @@ cd ..
1919
cd zig
2020
rm -rf zig-out .zig-cache > /dev/null 2>&1
2121
mkdir -p zig-out/bin
22-
zig build-exe src/main.zig -femit-bin=zig-out/bin/chat > /dev/null 2>&1
22+
zig build > /dev/null 2>&1
2323
cd ..
2424

2525
echo "Build complete!"
2626
echo ""
2727

28-
# Test C version
28+
# Test C17 version
2929
echo "=========================================="
30-
echo "C Version Testing & Benchmarking"
30+
echo "C17 Version Testing & Benchmarking"
3131
echo "=========================================="
3232
echo ""
3333
echo "Running with test inputs..."
3434

35-
C_START=$(date +%s%N)
36-
c/./chat < test_inputs.txt > /tmp/c_output.txt 2>&1
37-
C_END=$(date +%s%N)
38-
C_TIME=$(( (C_END - C_START) / 1000000 )) # milliseconds
35+
C17_START=$(date +%s%N)
36+
c/./chat-c17 < test_inputs.txt > /tmp/c17_output.txt 2>&1
37+
C17_END=$(date +%s%N)
38+
C17_TIME=$(( (C17_END - C17_START) / 1000000 )) # milliseconds
3939

4040
echo "Output:"
41-
cat /tmp/c_output.txt
41+
cat /tmp/c17_output.txt
4242
echo ""
43-
echo "Execution time: ${C_TIME}ms"
43+
echo "Execution time: ${C17_TIME}ms"
44+
echo ""
45+
46+
# Test C23 version
47+
echo "=========================================="
48+
echo "C23 Version Testing & Benchmarking"
49+
echo "=========================================="
50+
echo ""
51+
echo "Running with test inputs..."
52+
53+
C23_START=$(date +%s%N)
54+
c/./chat-c23 < test_inputs.txt > /tmp/c23_output.txt 2>&1
55+
C23_END=$(date +%s%N)
56+
C23_TIME=$(( (C23_END - C23_START) / 1000000 )) # milliseconds
57+
58+
echo "Output:"
59+
cat /tmp/c23_output.txt
60+
echo ""
61+
echo "Execution time: ${C23_TIME}ms"
4462
echo ""
4563

4664
# Test Zig version
@@ -67,16 +85,19 @@ echo "Output Comparison"
6785
echo "=========================================="
6886
echo ""
6987

70-
if diff -q /tmp/c_output.txt /tmp/zig_output.txt > /dev/null 2>&1; then
71-
echo "✓ Outputs are identical"
88+
OUTPUTS_MATCH=true
89+
if diff -q /tmp/c17_output.txt /tmp/c23_output.txt > /dev/null 2>&1; then
90+
echo "✓ C17 and C23 outputs are identical"
7291
else
73-
echo "✗ Outputs differ"
74-
echo ""
75-
echo "C output:"
76-
cat /tmp/c_output.txt
77-
echo ""
78-
echo "Zig output:"
79-
cat /tmp/zig_output.txt
92+
echo "✗ C17 and C23 outputs differ"
93+
OUTPUTS_MATCH=false
94+
fi
95+
96+
if diff -q /tmp/c17_output.txt /tmp/zig_output.txt > /dev/null 2>&1; then
97+
echo "✓ C17 and Zig outputs are identical"
98+
else
99+
echo "✗ C17 and Zig outputs differ"
100+
OUTPUTS_MATCH=false
80101
fi
81102

82103
echo ""
@@ -87,16 +108,47 @@ echo "Runtime Comparison"
87108
echo "=========================================="
88109
echo ""
89110

90-
if [ $C_TIME -lt $ZIG_TIME ]; then
91-
RATIO=$(echo "scale=2; $ZIG_TIME / $C_TIME" | bc)
92-
echo "C version is ${RATIO}x faster"
93-
else
94-
RATIO=$(echo "scale=2; $C_TIME / $ZIG_TIME" | bc)
95-
echo "Zig version is ${RATIO}x faster"
111+
# Find the fastest
112+
MIN_TIME=$C17_TIME
113+
FASTEST="C17"
114+
if [ $C23_TIME -lt $MIN_TIME ]; then
115+
MIN_TIME=$C23_TIME
116+
FASTEST="C23"
117+
fi
118+
if [ $ZIG_TIME -lt $MIN_TIME ]; then
119+
MIN_TIME=$ZIG_TIME
120+
FASTEST="Zig"
96121
fi
97122

123+
echo "Fastest: $FASTEST (${MIN_TIME}ms)"
98124
echo ""
99125
echo "Summary:"
100-
echo " C: ${C_TIME}ms"
126+
echo " C17: ${C17_TIME}ms"
127+
echo " C23: ${C23_TIME}ms"
101128
echo " Zig: ${ZIG_TIME}ms"
102129
echo ""
130+
131+
# Show ratios relative to fastest
132+
if [ $MIN_TIME -gt 0 ]; then
133+
C17_RATIO=$(echo "scale=2; $C17_TIME / $MIN_TIME" | bc)
134+
C23_RATIO=$(echo "scale=2; $C23_TIME / $MIN_TIME" | bc)
135+
ZIG_RATIO=$(echo "scale=2; $ZIG_TIME / $MIN_TIME" | bc)
136+
echo "Relative to fastest:"
137+
echo " C17: ${C17_RATIO}x"
138+
echo " C23: ${C23_RATIO}x"
139+
echo " Zig: ${ZIG_RATIO}x"
140+
echo ""
141+
fi
142+
143+
# Binary sizes
144+
echo "=========================================="
145+
echo "Binary Sizes"
146+
echo "=========================================="
147+
echo ""
148+
C17_SIZE=$(ls -lh c/chat-c17 | awk '{print $5}')
149+
C23_SIZE=$(ls -lh c/chat-c23 | awk '{print $5}')
150+
ZIG_SIZE=$(ls -lh zig/zig-out/bin/chat | awk '{print $5}')
151+
echo " C17: $C17_SIZE"
152+
echo " C23: $C23_SIZE"
153+
echo " Zig: $ZIG_SIZE"
154+
echo ""

zig/src/main.zig

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ pub fn main() !void {
3333
try stdout.interface.flush();
3434

3535
// Read line using Zig 0.15 delimiter API
36-
const line = stdin.interface.takeDelimiterExclusive('\n') catch |err| {
36+
// Use takeDelimiter which returns null on EOF with empty remaining
37+
const line = stdin.interface.takeDelimiter('\n') catch |err| {
3738
switch (err) {
38-
error.EndOfStream => break,
3939
error.StreamTooLong => {
4040
// Line too long, skip it
4141
continue;
@@ -44,8 +44,10 @@ pub fn main() !void {
4444
}
4545
};
4646

47-
const trimmed = std.mem.trim(u8, line, " \t\r\n");
48-
if (trimmed.len == 0) break;
47+
if (line == null) break;
48+
49+
const trimmed = std.mem.trim(u8, line.?, " \t\r\n");
50+
if (trimmed.len == 0) continue; // Empty line, keep going
4951

5052
var word_iter = std.mem.tokenizeAny(u8, trimmed, chatbot.SeparatorChars);
5153

0 commit comments

Comments
 (0)