Skip to content

Commit c348aed

Browse files
committed
[Android] Added support for wrapping a MemoryFile. Performance as low as using a memorymapped file
1 parent 7594a19 commit c348aed

File tree

3 files changed

+187
-6
lines changed

3 files changed

+187
-6
lines changed

main/android/uk/co/real_logic/sbe/codec/java/BitUtil.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
*/
1616
package uk.co.real_logic.sbe.codec.java;
1717

18+
import java.lang.reflect.Constructor;
1819
import java.lang.reflect.Field;
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Modifier;
1922
import java.nio.Buffer;
2023
import java.nio.ByteBuffer;
2124
import java.security.AccessController;
2225
import java.security.PrivilegedActionException;
2326
import java.security.PrivilegedExceptionAction;
2427

28+
import android.os.MemoryFile;
29+
2530
/**
2631
* Miscellaneous useful functions for dealing with low level bits and bytes.
2732
*/
@@ -34,6 +39,7 @@ final class BitUtil
3439
//at the same time, methods inside libcore.io.Memory where changed to use long addresses instead of ints
3540
//https://android.googlesource.com/platform/libcore/+/0121106d9dc1ba713b53822886355e4d9339e852%5E%21/
3641
// luni/src/main/java/java/nio/Buffer.java
42+
//at the same time (api 18) ReadWriteDirectByteBuffer was merged into DirectByteBuffer
3743
//
3844
//In api 14 org/apache/harmony/luni/platform/OSMemory.java was renamed to libcore/io/Memory.java
3945
//https://android.googlesource.com/platform/libcore/+/f934c3d2c8dd9e6bc5299cef41adace2a671637d
@@ -47,6 +53,10 @@ final class BitUtil
4753
private static final MemoryAccess MEMORY_ACCESS;
4854
private static final boolean USE_LONG_ADDRESS;
4955

56+
private static final Field MEMORYFILE_ADDRESS_FIELD;
57+
private static final boolean USE_LONG_MEMORYFILE_ADDRESS;
58+
private static final Constructor<?> DIRECT_BYTE_BUFFER_CONSTRUCTOR;
59+
5060
static
5161
{
5262
try
@@ -55,6 +65,32 @@ final class BitUtil
5565

5666
USE_LONG_ADDRESS = EFFECTIVE_DIRECT_ADDRESS_FIELD.getType() == long.class;
5767
MEMORY_ACCESS = USE_LONG_ADDRESS ? new MemoryAccessLongAddress() : new MemoryAccessIntAddress();
68+
69+
MEMORYFILE_ADDRESS_FIELD = getField(MemoryFile.class, "mAddress");
70+
USE_LONG_MEMORYFILE_ADDRESS = MEMORYFILE_ADDRESS_FIELD.getType() == long.class;
71+
72+
final PrivilegedExceptionAction<Constructor<?>> action = new PrivilegedExceptionAction<Constructor<?>>()
73+
{
74+
public Constructor<?> run() throws Exception
75+
{
76+
Class<?> clazz = Class.forName("java.nio.DirectByteBuffer");
77+
Constructor<?> constructor;
78+
if (Modifier.isAbstract(clazz.getModifiers()))
79+
{
80+
//use this starting with api level < 18
81+
clazz = Class.forName("java.nio.ReadWriteDirectByteBuffer");
82+
constructor = clazz.getDeclaredConstructor(int.class, int.class);
83+
}
84+
else
85+
{
86+
//use this starting with api level >= 18
87+
constructor = clazz.getDeclaredConstructor(long.class, int.class);
88+
}
89+
constructor.setAccessible(true);
90+
return constructor;
91+
}
92+
};
93+
DIRECT_BYTE_BUFFER_CONSTRUCTOR = AccessController.doPrivileged(action);
5894
}
5995
catch (final Exception ex)
6096
{
@@ -83,6 +119,23 @@ static long getEffectiveDirectAddress(final ByteBuffer buffer)
83119
}
84120
}
85121

122+
static long getMemoryFileAddress(final MemoryFile memoryFile)
123+
{
124+
try
125+
{
126+
return USE_LONG_MEMORYFILE_ADDRESS ? MEMORYFILE_ADDRESS_FIELD
127+
.getLong(memoryFile) : MEMORYFILE_ADDRESS_FIELD.getInt(memoryFile);
128+
}
129+
catch (final IllegalArgumentException e)
130+
{
131+
return 0;
132+
}
133+
catch (final IllegalAccessException e)
134+
{
135+
return 0;
136+
}
137+
}
138+
86139
/**
87140
* Get the instance of {@link MemoryAccess}.
88141
*
@@ -115,4 +168,42 @@ public Field run() throws Exception
115168

116169
return AccessController.doPrivileged(action);
117170
}
171+
172+
/**
173+
* Builds a DirectByteBuffer out of an adddress and a size
174+
*
175+
* @param address
176+
* @param size
177+
* @return
178+
*/
179+
static ByteBuffer newDirectByteBuffer(long address, int size)
180+
{
181+
try
182+
{
183+
if (USE_LONG_ADDRESS)
184+
{
185+
return (ByteBuffer) DIRECT_BYTE_BUFFER_CONSTRUCTOR.newInstance(address, size);
186+
}
187+
else
188+
{
189+
return (ByteBuffer) DIRECT_BYTE_BUFFER_CONSTRUCTOR.newInstance((int) address, size);
190+
}
191+
}
192+
catch (final IllegalArgumentException e)
193+
{
194+
return null;
195+
}
196+
catch (final IllegalAccessException e)
197+
{
198+
return null;
199+
}
200+
catch (InstantiationException e)
201+
{
202+
return null;
203+
}
204+
catch (InvocationTargetException e)
205+
{
206+
return null;
207+
}
208+
}
118209
}

main/android/uk/co/real_logic/sbe/codec/java/DirectBuffer.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.nio.ByteOrder;
2020
import java.util.Locale;
2121

22+
import android.os.MemoryFile;
2223
import libcore.io.Memory;
2324

2425
/**
@@ -34,6 +35,9 @@ public final class DirectBuffer
3435
private boolean hasArray;
3536

3637
private ByteBuffer byteBuffer;
38+
//we keep this reference to avoid being cleaned by GC
39+
@SuppressWarnings("unused")
40+
private MemoryFile memoryFile;
3741
private long effectiveDirectAddress;
3842
private int offset;
3943
private int capacity;
@@ -59,6 +63,29 @@ public DirectBuffer(final ByteBuffer buffer)
5963
wrap(buffer);
6064
}
6165

66+
/**
67+
* Attach a view to an off-heap memory region by address.
68+
*
69+
* <b>This constructor is not available under Android.</b>
70+
*
71+
* @param address where the memory begins off-heap
72+
* @param capacity of the buffer from the given address
73+
*/
74+
public DirectBuffer(final long address, final int capacity)
75+
{
76+
wrap(address, capacity);
77+
}
78+
79+
/**
80+
* Attach a view to a {@link MemoryFile} for providing direct access.
81+
*
82+
* @param memoryFile to which the view is attached.
83+
*/
84+
public DirectBuffer(final MemoryFile memoryFile)
85+
{
86+
wrap(memoryFile);
87+
}
88+
6289
/**
6390
* Attach a view to a byte[] for providing direct access.
6491
*
@@ -72,6 +99,7 @@ public void wrap(final byte[] buffer)
7299
byteArray = buffer;
73100
hasArray = true;
74101
byteBuffer = null;
102+
memoryFile = null;
75103
}
76104

77105
/**
@@ -101,10 +129,41 @@ public void wrap(final ByteBuffer buffer)
101129
offset = 0;
102130
effectiveDirectAddress = BitUtil.getEffectiveDirectAddress(buffer);
103131
}
104-
132+
memoryFile = null;
105133
capacity = buffer.capacity();
106134
}
107135

136+
/**
137+
* Attach a view to an off-heap memory region by address.
138+
*
139+
* <b>This method is not available under Android.</b>
140+
*
141+
* @param address where the memory begins off-heap
142+
* @param capacity of the buffer from the given address
143+
*/
144+
public void wrap(final long address, final int capacity)
145+
{
146+
effectiveDirectAddress = address;
147+
this.capacity = capacity;
148+
byteArray = null;
149+
hasArray = false;
150+
memoryFile = null;
151+
//Memory.memmove needs either a bytebuffer or a bytearray
152+
//it could only work with memory addresses, but it doesn't
153+
byteBuffer = BitUtil.newDirectByteBuffer(effectiveDirectAddress, this.capacity);
154+
}
155+
156+
/**
157+
* Attach a view to a {@link MemoryFile} for providing direct access.
158+
*
159+
* @param memoryFile to which the view is attached.
160+
*/
161+
public void wrap(final MemoryFile memoryFile)
162+
{
163+
wrap(BitUtil.getMemoryFileAddress(memoryFile), memoryFile.length());
164+
this.memoryFile = memoryFile;
165+
}
166+
108167
/**
109168
* Return the underlying byte[] for this buffer or null if none is attached.
110169
*

test/android/uk/co/real_logic/sbe/codec/java/DirectBufferTest.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.rules.ExpectedException;
2424
import org.junit.runner.RunWith;
2525

26+
import android.os.MemoryFile;
2627
import android.test.suitebuilder.annotation.SmallTest;
2728
import android.util.Log;
2829

@@ -79,13 +80,17 @@ public class DirectBufferTest
7980
@DataPoint
8081
public static final DirectBuffer MEMORY_MAPPED_BUFFER = new DirectBuffer(createMemoryMappedBuffer());
8182

82-
// private static long memoryBlockAddress = 0;
83+
private static ByteBuffer offHeapDummyBuffer = ByteBuffer.allocateDirect(BUFFER_CAPACITY);
84+
private static long memoryBlockAddress = BitUtil.getEffectiveDirectAddress(offHeapDummyBuffer);
8385

84-
// @DataPoint //not valid for android
85-
// public static final DirectBuffer OFF_HEAP_BUFFER =
86-
// new DirectBuffer(memoryBlockAddress, BUFFER_CAPACITY);
86+
@DataPoint
87+
public static final DirectBuffer OFF_HEAP_BUFFER = new DirectBuffer(memoryBlockAddress, BUFFER_CAPACITY);
8788

88-
@Theory
89+
@DataPoint //not valid for jdk
90+
public static final DirectBuffer MEMORY_FILE_BUFFER =
91+
new DirectBuffer(createMemoryFile());
92+
93+
@Theory
8994
public void shouldThrowExceptionForLimitAboveCapacity(final DirectBuffer buffer)
9095
{
9196
exceptionRule.expect(IndexOutOfBoundsException.class);
@@ -521,8 +526,33 @@ public void shouldPutDirectBytesToBufferTruncate(final DirectBuffer buffer)
521526
assertThat(Integer.valueOf(result), is(Integer.valueOf(15)));
522527
}
523528

529+
@Theory
530+
public void shouldTestHelloMemoryFile() throws IOException
531+
{
532+
final byte[] testBytes = "Hello World!".getBytes();
533+
MEMORY_FILE_BUFFER.putBytes(10, testBytes, 6, 6);
534+
byte[] result = new byte[6];
535+
memoryFile.readBytes(result, 10, 0, 6);
536+
String s = new String(result);
537+
assertThat(s, is("World!"));
538+
}
539+
524540
private static RandomAccessFile memoryMappedFile;
525541
private static MappedByteBuffer buffer;
542+
private static MemoryFile memoryFile;
543+
544+
private static MemoryFile createMemoryFile()
545+
{
546+
try
547+
{
548+
memoryFile = new MemoryFile("gigica", BUFFER_CAPACITY);
549+
}
550+
catch (IOException e)
551+
{
552+
memoryFile = null;
553+
}
554+
return memoryFile;
555+
}
526556

527557
private static ByteBuffer createMemoryMappedBuffer()
528558
{
@@ -553,6 +583,7 @@ public static void cleanup()
553583
cleanMethod.invoke(buffer);
554584

555585
memoryMappedFile.close();
586+
memoryFile.close();
556587
}
557588
catch (Exception e)
558589
{

0 commit comments

Comments
 (0)