Skip to content

Commit eca249b

Browse files
committed
Documentation.
1 parent 122dce4 commit eca249b

8 files changed

Lines changed: 779 additions & 10 deletions

File tree

context/getting-started.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Getting Started
2+
3+
This guide explains how to use `io-stream` to add efficient buffering to Ruby IO objects.
4+
5+
## Overview
6+
7+
`io-stream` provides a buffered stream wrapper for any IO-like object in Ruby. It wraps standard Ruby IO instances (files, sockets, pipes) and adds buffering for both reading and writing operations, significantly improving performance for applications that perform many small reads or writes.
8+
9+
## Installation
10+
11+
Add the gem to your project:
12+
13+
~~~ bash
14+
$ bundle add io-stream
15+
~~~
16+
17+
## Core Concepts
18+
19+
### Buffered Streams
20+
21+
`io-stream` provides buffering through the {IO::Stream::Buffered} class, which wraps any IO object. Buffering reduces the number of system calls by accumulating data in memory before actually reading from or writing to the underlying IO.
22+
23+
### Read and Write Buffers
24+
25+
The stream maintains separate buffers for reading and writing:
26+
27+
- **Read buffer**: Accumulates data from the underlying IO, allowing multiple small reads without system calls
28+
- **Write buffer**: Accumulates data to write, flushing to the underlying IO only when the buffer is full or explicitly flushed
29+
30+
## Basic Usage
31+
32+
### Wrapping an IO Object
33+
34+
You can wrap any IO-like object using {IO::Stream}:
35+
36+
~~~ ruby
37+
require 'io/stream'
38+
39+
# Wrap a file
40+
file = File.open("data.txt", "w+")
41+
stream = IO::Stream(file)
42+
43+
# Wrap a socket
44+
require 'socket'
45+
socket = TCPSocket.new("example.com", 80)
46+
stream = IO::Stream(socket)
47+
~~~
48+
49+
### Opening Files Directly
50+
51+
You can also open files directly as buffered streams:
52+
53+
~~~ ruby
54+
require 'io/stream'
55+
56+
# Open a file for reading
57+
stream = IO::Stream::Buffered.open("data.txt", "r")
58+
data = stream.read
59+
stream.close
60+
61+
# Open with a block (auto-closes)
62+
IO::Stream::Buffered.open("data.txt", "w") do |stream|
63+
stream.write("Hello, World!")
64+
stream.flush
65+
end
66+
~~~
67+
68+
### Reading Data
69+
70+
The {IO::Stream::Readable} module provides various methods for reading:
71+
72+
~~~ ruby
73+
require 'io/stream'
74+
75+
IO::Stream::Buffered.open("data.txt", "r") do |stream|
76+
# Read entire stream
77+
content = stream.read
78+
79+
# Read specific number of bytes
80+
chunk = stream.read(1024)
81+
82+
# Read a line
83+
line = stream.gets
84+
85+
# Read all lines
86+
lines = stream.readlines
87+
88+
# Check for end of stream
89+
if stream.eof?
90+
puts "Reached end of file"
91+
end
92+
end
93+
~~~
94+
95+
### Writing Data
96+
97+
The {IO::Stream::Writable} module provides methods for writing:
98+
99+
~~~ ruby
100+
require 'io/stream'
101+
102+
IO::Stream::Buffered.open("output.txt", "w") do |stream|
103+
# Write data (buffered)
104+
stream.write("Hello, ")
105+
stream.write("World!")
106+
107+
# Write with automatic newline
108+
stream.puts("This is a line")
109+
110+
# Flush buffer to ensure data is written
111+
stream.flush
112+
end
113+
~~~
114+
115+
## Important Behaviors
116+
117+
### Automatic Flushing
118+
119+
The write buffer automatically flushes when:
120+
121+
- The buffer size reaches the minimum write size (default: 64KB).
122+
- You call {IO::Stream::Writable#puts} (always flushes immediately).
123+
- You call {IO::Stream::Writable#flush} explicitly.
124+
- The stream is closed.
125+
126+
### Manual Flushing
127+
128+
For applications that need precise control over when data is written:
129+
130+
~~~ ruby
131+
stream.write("Important data")
132+
stream.flush # Ensure data is written immediately
133+
~~~
134+
135+
### Buffer Sizes
136+
137+
You can customize buffer sizes when creating streams:
138+
139+
~~~ ruby
140+
# Smaller buffer for interactive applications
141+
stream = IO::Stream::Buffered.new(io, minimum_write_size: 4096)
142+
143+
# Larger buffer for bulk operations
144+
stream = IO::Stream::Buffered.new(io, minimum_write_size: 256 * 1024)
145+
~~~

context/high-performance-io.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# High Performance IO
2+
3+
This guide explains how to achieve optimal performance when using `io-stream` by understanding and controlling flush behavior.
4+
5+
## Overview
6+
7+
The key to high-performance IO with `io-stream` is understanding when and how to flush your write buffer. Improper flush timing can significantly impact throughput, latency, and CPU usage. This guide helps you choose the right buffering strategy for your application.
8+
9+
## Why Buffering Matters
10+
11+
Every write to an underlying IO object (file, socket, pipe) involves a system call, which has overhead:
12+
13+
- **Context switching**: Transferring control between userspace and kernel space.
14+
- **System call overhead**: The cost of invoking kernel functions.
15+
- **Network packet overhead**: For sockets, each small write may trigger a separate packet.
16+
17+
Buffering solves this by accumulating data in memory and performing larger, less frequent writes. However, buffering introduces latency - data sits in memory until flushed.
18+
19+
Use buffering when you need:
20+
- **High throughput**: Maximize data transfer rate for bulk operations.
21+
- **Reduced CPU usage**: Minimize system call overhead when writing many small pieces.
22+
- **Efficient network utilization**: Avoid sending many tiny packets.
23+
24+
## The Flush/Throughput Tradeoff
25+
26+
There's a fundamental tradeoff between responsiveness and throughput:
27+
28+
```mermaid
29+
graph LR
30+
A[Immediate Flush] -->|Low Latency| B[Responsive]
31+
A -->|Many System Calls| C[Lower Throughput]
32+
D[Delayed Flush] -->|Higher Latency| E[Buffered]
33+
D -->|Fewer System Calls| F[Higher Throughput]
34+
```
35+
36+
**Immediate flushing** (after every write):
37+
- ✅ Data is sent immediately - low latency.
38+
- ✅ Simple mental model - predictable behavior.
39+
- ❌ High system call overhead.
40+
- ❌ Lower maximum throughput.
41+
- ❌ More CPU usage.
42+
- ❌ Network inefficiency (many small packets).
43+
44+
**Buffered flushing** (accumulate before sending):
45+
- ✅ Fewer system calls - higher throughput.
46+
- ✅ Better CPU efficiency.
47+
- ✅ More efficient network packet utilization.
48+
- ❌ Data is delayed - higher latency.
49+
- ❌ Requires careful flush management.
50+
51+
## Automatic Flush Behavior
52+
53+
`io-stream` automatically flushes in these situations:
54+
55+
~~~ ruby
56+
# 1. Buffer reaches minimum_write_size (default: 64KB)
57+
stream.write("x" * 65536) # Automatically flushes
58+
59+
# 2. Using puts() always flushes
60+
stream.puts("This is flushed immediately")
61+
62+
# 3. Closing the stream
63+
stream.close # Flushes any remaining data
64+
~~~
65+
66+
## Choosing Your Flush Strategy
67+
68+
### Strategy 1: Let Automatic Flushing Handle It
69+
70+
Best for: Bulk data transfer, file processing, log writing.
71+
72+
~~~ ruby
73+
require 'io/stream'
74+
75+
# Default behavior - automatic flush at 64KB
76+
stream = IO::Stream::Buffered.open("large_file.dat", "w")
77+
78+
# Write lots of data
79+
1000.times do |i|
80+
stream.write("Record #{i}\n" * 1000)
81+
end
82+
83+
stream.close # Final flush on close
84+
~~~
85+
86+
**When to use:**
87+
- Writing large amounts of data continuously.
88+
- Throughput is more important than latency.
89+
- You don't need interactive feedback.
90+
91+
### Strategy 2: Manual Flush at Logical Boundaries
92+
93+
Best for: Request/response protocols, transaction processing, structured logging.
94+
95+
~~~ ruby
96+
require 'io/stream'
97+
require 'socket'
98+
99+
socket = TCPSocket.new("example.com", 80)
100+
stream = IO::Stream(socket)
101+
102+
# Build complete HTTP request
103+
stream.write("GET / HTTP/1.1\r\n")
104+
stream.write("Host: example.com\r\n")
105+
stream.write("Connection: close\r\n")
106+
stream.write("\r\n")
107+
108+
# Flush after complete request
109+
stream.flush # Send request as one operation
110+
~~~
111+
112+
**When to use:**
113+
- Message-based protocols (HTTP, Redis, etc.)
114+
- You need to send complete "units" of data
115+
- Each logical operation should complete atomically
116+
- Balance between throughput and responsiveness
117+
118+
### Strategy 3: Immediate Flush for Interactive Applications
119+
120+
Best for: Chat applications, streaming responses, real-time dashboards.
121+
122+
~~~ ruby
123+
require 'io/stream'
124+
125+
# Use smaller buffer for more frequent automatic flushes
126+
stream = IO::Stream::Buffered.new(
127+
socket,
128+
minimum_write_size: 512 # Smaller buffer = more responsive
129+
)
130+
131+
# Or flush after every message
132+
stream.write(message)
133+
stream.flush # Ensure immediate delivery
134+
~~~
135+
136+
**When to use:**
137+
- Real-time user interaction required.
138+
- Low latency is critical.
139+
- Data arrives in small, discrete chunks.
140+
141+
### Strategy 4: Time-Based Flushing
142+
143+
Best for: Streaming data, progress updates, monitoring
144+
145+
~~~ ruby
146+
require 'io/stream'
147+
148+
stream = IO::Stream::Buffered.open("stream.log", "w")
149+
last_flush = Time.now
150+
151+
loop do
152+
stream.write(generate_log_entry)
153+
154+
# Flush every second or when buffer is large
155+
if Time.now - last_flush > 1.0
156+
stream.flush
157+
last_flush = Time.now
158+
end
159+
end
160+
~~~
161+
162+
**When to use:**
163+
- Ensuring regular progress visibility.
164+
- Protecting against data loss (periodic flush to disk).
165+
- Streaming applications with real-time monitoring.
166+
167+
### Strategy 5: Readiness based flushing
168+
169+
Best for: interactive protocols, terminal applications, chat servers.
170+
171+
~~~ ruby
172+
require 'io/stream'
173+
174+
stream = IO::Stream::Buffered.new(socket, minimum_write_size: 1024)
175+
176+
loop do
177+
# Blocking read from a queue of messages to send:
178+
chunk = queue.pop
179+
stream.write(chunk)
180+
181+
if queue.empty?
182+
# Flush when we are likely to block on the queue:
183+
stream.flush
184+
end
185+
end
186+
~~~
187+
188+
**When to use:**
189+
- When you have unpredictable message arrival patterns.
190+
- When you want to ensure the lowest possible latency while still benefiting from buffering when messages arrive in bursts.
191+
192+
## Buffer Size Configuration
193+
194+
The `minimum_write_size` parameter controls when automatic flushing occurs:
195+
196+
~~~ ruby
197+
# Very small buffer - more responsive, lower throughput
198+
stream = IO::Stream::Buffered.new(io, minimum_write_size: 1024)
199+
200+
# Default - balanced (64KB)
201+
stream = IO::Stream::Buffered.new(io)
202+
203+
# Large buffer - maximum throughput, higher latency
204+
stream = IO::Stream::Buffered.new(io, minimum_write_size: 512 * 1024)
205+
~~~
206+
207+
### Choosing Buffer Size
208+
209+
**Small buffers (1-8KB):**
210+
- Interactive protocols (terminal, chat).
211+
- Real-time data visualization.
212+
- Acceptable: Lower throughput.
213+
214+
**Medium buffers (8-64KB):**
215+
- Web servers (default is good).
216+
- Application servers.
217+
- Database connections.
218+
- Balance of throughput and responsiveness.
219+
220+
**Large buffers (64KB-1MB):**
221+
- File processing.
222+
- Bulk data transfer.
223+
- Video encoding.
224+
- Logging systems.
225+
- Only latency-insensitive applications.

context/index.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Automatically generated context index for Utopia::Project guides.
2+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3+
---
4+
description: Provides a generic stream wrapper for IO instances.
5+
metadata:
6+
documentation_uri: https://socketry.github.io/io-stream/
7+
source_code_uri: https://github.com/socketry/io-stream.git
8+
files:
9+
- path: getting-started.md
10+
title: Getting Started
11+
description: This guide explains how to use `io-stream` to add efficient buffering
12+
to Ruby IO objects.
13+
- path: high-performance-io.md
14+
title: High Performance IO
15+
description: This guide explains how to achieve optimal performance when using `io-stream`
16+
by understanding and controlling flush behavior.

0 commit comments

Comments
 (0)