Skip to content

Commit a7335a4

Browse files
committed
Add pooled ByteBuffer allocator with size classes
Introduce PooledByteBufferAllocator with global buckets and per-thread caches and use it in HTTP/2 FrameFactory.
1 parent 1ee9e08 commit a7335a4

File tree

5 files changed

+770
-0
lines changed

5 files changed

+770
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.core5.benchmark;
28+
29+
import java.nio.ByteBuffer;
30+
import java.util.concurrent.TimeUnit;
31+
32+
import org.apache.hc.core5.io.ByteBufferAllocator;
33+
import org.apache.hc.core5.io.PooledByteBufferAllocator;
34+
import org.apache.hc.core5.io.SimpleByteBufferAllocator;
35+
import org.openjdk.jmh.annotations.Benchmark;
36+
import org.openjdk.jmh.annotations.BenchmarkMode;
37+
import org.openjdk.jmh.annotations.Fork;
38+
import org.openjdk.jmh.annotations.Measurement;
39+
import org.openjdk.jmh.annotations.Mode;
40+
import org.openjdk.jmh.annotations.OutputTimeUnit;
41+
import org.openjdk.jmh.annotations.Param;
42+
import org.openjdk.jmh.annotations.Scope;
43+
import org.openjdk.jmh.annotations.Setup;
44+
import org.openjdk.jmh.annotations.State;
45+
import org.openjdk.jmh.annotations.Threads;
46+
import org.openjdk.jmh.annotations.Warmup;
47+
import org.openjdk.jmh.infra.Blackhole;
48+
49+
@BenchmarkMode(Mode.Throughput)
50+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
51+
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
52+
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
53+
@Fork(1)
54+
public class ByteBufferAllocatorBenchmark {
55+
56+
/**
57+
* Per-thread state: each thread gets its own allocators.
58+
* This measures the best-case hot-path behaviour with no contention
59+
* on the pooled allocator.
60+
*/
61+
@State(Scope.Thread)
62+
public static class ThreadLocalState {
63+
64+
@Param({"1024", "8192", "65536"})
65+
public int bufferSize;
66+
67+
@Param({"100"})
68+
public int iterations;
69+
70+
public ByteBufferAllocator simpleAllocator;
71+
public ByteBufferAllocator pooledAllocator;
72+
73+
public byte[] payload;
74+
75+
@Setup
76+
public void setUp() {
77+
this.simpleAllocator = SimpleByteBufferAllocator.INSTANCE;
78+
this.pooledAllocator = new PooledByteBufferAllocator(
79+
1024, // min pooled size
80+
256 * 1024, // max pooled size
81+
1024, // maxGlobalPerBucket
82+
64 // maxLocalPerBucket
83+
);
84+
this.payload = new byte[bufferSize / 2];
85+
for (int i = 0; i < this.payload.length; i++) {
86+
this.payload[i] = (byte) (i & 0xFF);
87+
}
88+
}
89+
90+
}
91+
92+
/**
93+
* Shared state: all threads share the same allocators.
94+
* This measures contention on the global buckets and any shared
95+
* structures inside the pooled allocator.
96+
*/
97+
@State(Scope.Benchmark)
98+
public static class SharedState {
99+
100+
@Param({"1024", "8192", "65536"})
101+
public int bufferSize;
102+
103+
@Param({"100"})
104+
public int iterations;
105+
106+
public ByteBufferAllocator simpleAllocator;
107+
public ByteBufferAllocator pooledAllocator;
108+
109+
public byte[] payload;
110+
111+
@Setup
112+
public void setUp() {
113+
this.simpleAllocator = SimpleByteBufferAllocator.INSTANCE;
114+
this.pooledAllocator = new PooledByteBufferAllocator(
115+
1024,
116+
256 * 1024,
117+
1024,
118+
64
119+
);
120+
this.payload = new byte[bufferSize / 2];
121+
for (int i = 0; i < this.payload.length; i++) {
122+
this.payload[i] = (byte) (i & 0xFF);
123+
}
124+
}
125+
126+
}
127+
128+
// --------- Per-thread allocators (your original semantics) ---------
129+
130+
@Benchmark
131+
public void pooled_allocator_thread_local(final ThreadLocalState state, final Blackhole blackhole) {
132+
final int bufferSize = state.bufferSize;
133+
final int iterations = state.iterations;
134+
final ByteBufferAllocator allocator = state.pooledAllocator;
135+
final byte[] payload = state.payload;
136+
137+
for (int i = 0; i < iterations; i++) {
138+
final ByteBuffer buf = allocator.allocate(bufferSize);
139+
buf.put(payload);
140+
buf.flip();
141+
blackhole.consume(buf.get(0));
142+
allocator.release(buf);
143+
}
144+
}
145+
146+
@Benchmark
147+
public void simple_allocator_thread_local(final ThreadLocalState state, final Blackhole blackhole) {
148+
final int bufferSize = state.bufferSize;
149+
final int iterations = state.iterations;
150+
final ByteBufferAllocator allocator = state.simpleAllocator;
151+
final byte[] payload = state.payload;
152+
153+
for (int i = 0; i < iterations; i++) {
154+
final ByteBuffer buf = allocator.allocate(bufferSize);
155+
buf.put(payload);
156+
buf.flip();
157+
blackhole.consume(buf.get(0));
158+
allocator.release(buf);
159+
}
160+
}
161+
162+
// --------- Shared allocator, multi-threaded contention ---------
163+
164+
/**
165+
* Run this with multiple threads, e.g.:
166+
* -t 4 or -t 8 on the JMH command line.
167+
*/
168+
@Benchmark
169+
@Threads(4) // Override from the command line if you want: -t 8, etc.
170+
public void pooled_allocator_shared(final SharedState state, final Blackhole blackhole) {
171+
final int bufferSize = state.bufferSize;
172+
final int iterations = state.iterations;
173+
final ByteBufferAllocator allocator = state.pooledAllocator;
174+
final byte[] payload = state.payload;
175+
176+
for (int i = 0; i < iterations; i++) {
177+
final ByteBuffer buf = allocator.allocate(bufferSize);
178+
buf.put(payload);
179+
buf.flip();
180+
blackhole.consume(buf.get(0));
181+
allocator.release(buf);
182+
}
183+
}
184+
185+
@Benchmark
186+
@Threads(4)
187+
public void simple_allocator_shared(final SharedState state, final Blackhole blackhole) {
188+
final int bufferSize = state.bufferSize;
189+
final int iterations = state.iterations;
190+
final ByteBufferAllocator allocator = state.simpleAllocator;
191+
final byte[] payload = state.payload;
192+
193+
for (int i = 0; i < iterations; i++) {
194+
final ByteBuffer buf = allocator.allocate(bufferSize);
195+
buf.put(payload);
196+
buf.flip();
197+
blackhole.consume(buf.get(0));
198+
allocator.release(buf);
199+
}
200+
}
201+
202+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.core5.io;
28+
29+
import java.nio.ByteBuffer;
30+
31+
import org.apache.hc.core5.annotation.Contract;
32+
import org.apache.hc.core5.annotation.ThreadingBehavior;
33+
34+
/**
35+
* Strategy for allocating and releasing {@link ByteBuffer} instances.
36+
* <p>
37+
* Implementations may allocate fresh buffers on every call or reuse
38+
* buffers from a pool.
39+
*
40+
* @since 5.4
41+
*/
42+
@Contract(threading = ThreadingBehavior.STATELESS)
43+
public interface ByteBufferAllocator {
44+
45+
/**
46+
* Allocates a new heap buffer of the given capacity.
47+
*
48+
* @param capacity buffer capacity in bytes; non-negative.
49+
* @return a heap {@link ByteBuffer} with the given capacity.
50+
*/
51+
ByteBuffer allocate(int capacity);
52+
53+
/**
54+
* Allocates a new direct buffer of the given capacity.
55+
*
56+
* @param capacity buffer capacity in bytes; non-negative.
57+
* @return a direct {@link ByteBuffer} with the given capacity.
58+
*/
59+
ByteBuffer allocateDirect(int capacity);
60+
61+
/**
62+
* Releases a buffer back to the allocator.
63+
* <p>
64+
* Implementations that do not pool buffers may choose to ignore
65+
* this call.
66+
*
67+
* @param buffer the buffer to release; may be {@code null}.
68+
*/
69+
void release(ByteBuffer buffer);
70+
71+
}

0 commit comments

Comments
 (0)