Skip to content

Commit d301850

Browse files
committed
Aggressively avoid object creation during message read handling
1 parent b5aff63 commit d301850

File tree

1 file changed

+58
-5
lines changed

1 file changed

+58
-5
lines changed

src/main/java/org/ldk/batteries/NioPeerHandler.java

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.ldk.batteries;
22

3+
import org.ldk.impl.bindings;
34
import org.ldk.structs.*;
45

56
import java.io.IOException;
7+
import java.lang.reflect.Field;
68
import java.util.LinkedList;
79
import java.net.SocketAddress;
810
import java.net.StandardSocketOptions;
@@ -17,6 +19,7 @@
1719
public class NioPeerHandler {
1820
private static class Peer {
1921
SocketDescriptor descriptor;
22+
long descriptor_raw_pointer;
2023
SelectionKey key;
2124
}
2225

@@ -45,6 +48,19 @@ private void do_selector_action(SelectorCall meth) throws IOException {
4548
}
4649
}
4750

51+
static private Field CommonBasePointer;
52+
static {
53+
try {
54+
Class c = PeerManager.class.getSuperclass();
55+
CommonBasePointer = c.getDeclaredField("ptr");
56+
CommonBasePointer.setAccessible(true);
57+
long _dummy_check = CommonBasePointer.getLong(Ping.of((short)0, (short)0));
58+
} catch (NoSuchFieldException | IllegalAccessException e) {
59+
throw new IllegalArgumentException(
60+
"We currently use reflection to access protected fields as Java has no reasonable access controls", e);
61+
}
62+
}
63+
4864
private Peer setup_socket(SocketChannel chan) throws IOException {
4965
chan.configureBlocking(false);
5066
// Lightning tends to send a number of small messages back and forth between peers quickly, which Nagle is
@@ -89,6 +105,12 @@ public void disconnect_socket() {
89105
@Override public long hash() { return our_id; }
90106
});
91107
peer.descriptor = descriptor;
108+
try {
109+
peer.descriptor_raw_pointer = CommonBasePointer.getLong(descriptor);
110+
} catch (IllegalAccessException e) {
111+
throw new IllegalArgumentException(
112+
"We currently use reflection to access protected fields as Java has no reasonable access controls", e);
113+
}
92114
return peer;
93115
}
94116

@@ -108,7 +130,16 @@ public NioPeerHandler(PeerManager manager) throws IOException {
108130
this.peer_manager = manager;
109131
this.selector = Selector.open();
110132
io_thread = new Thread(() -> {
111-
ByteBuffer buf = ByteBuffer.allocate(8192);
133+
int BUF_SZ = 16 * 1024;
134+
byte[] max_buf_byte_object = new byte[BUF_SZ];
135+
ByteBuffer buf = ByteBuffer.allocate(BUF_SZ);
136+
137+
long peer_manager_raw_pointer;
138+
try {
139+
peer_manager_raw_pointer = CommonBasePointer.getLong(this.peer_manager);
140+
} catch (IllegalAccessException e) {
141+
throw new RuntimeException(e);
142+
}
112143
while (true) {
113144
try {
114145
if (IS_ANDROID) {
@@ -168,17 +199,32 @@ public NioPeerHandler(PeerManager manager) throws IOException {
168199
key.cancel();
169200
} else if (read > 0) {
170201
((Buffer)buf).flip();
171-
byte[] read_bytes = new byte[read];
202+
// This code is quite hot during initial network graph sync, so we go a ways out of
203+
// our way to avoid object allocations that'll make the GC sweat later -
204+
// * when we're hot, we'll likely often be reading the full buffer, so we keep
205+
// around a full-buffer-sized byte array to reuse across reads,
206+
// * We use the manual memory management call logic directly in bindings instead of
207+
// the nice "human-readable" wrappers. This puts us at risk of memory issues,
208+
// so we indirectly ensure compile fails if the types change by writing the
209+
// "human-readable" form of the same code in the dummy function below.
210+
byte[] read_bytes;
211+
if (read == BUF_SZ) {
212+
read_bytes = max_buf_byte_object;
213+
} else {
214+
read_bytes = new byte[read];
215+
}
172216
buf.get(read_bytes, 0, read);
173-
Result_boolPeerHandleErrorZ res = this.peer_manager.read_event(peer.descriptor, read_bytes);
174-
if (res instanceof Result_boolPeerHandleErrorZ.Result_boolPeerHandleErrorZ_OK) {
175-
if (((Result_boolPeerHandleErrorZ.Result_boolPeerHandleErrorZ_OK) res).res) {
217+
long read_result_pointer = bindings.PeerManager_read_event(
218+
peer_manager_raw_pointer, peer.descriptor_raw_pointer, read_bytes);
219+
if (bindings.LDKCResult_boolPeerHandleErrorZ_result_ok(read_result_pointer)) {
220+
if (bindings.LDKCResult_boolPeerHandleErrorZ_get_ok(read_result_pointer)) {
176221
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
177222
}
178223
} else {
179224
key.channel().close();
180225
key.cancel();
181226
}
227+
bindings.CResult_boolPeerHandleErrorZ_free(read_result_pointer);
182228
}
183229
}
184230
} catch (IOException ignored) {
@@ -198,6 +244,13 @@ public NioPeerHandler(PeerManager manager) throws IOException {
198244
io_thread.start();
199245
}
200246

247+
// Ensure the types used in the above manual code match what they were when the code was written.
248+
// Ensure the above manual bindings.* code changes if this fails to compile.
249+
private void dummy_check_return_type_matches_manual_memory_code_above(Peer peer) {
250+
byte[] read_bytes = new byte[32];
251+
Result_boolPeerHandleErrorZ res = this.peer_manager.read_event(peer.descriptor, read_bytes);
252+
}
253+
201254
/**
202255
* Connect to a peer given their node id and socket address. Blocks until a connection is established (or returns
203256
* IOException) and then the connection handling runs in the background.

0 commit comments

Comments
 (0)