├── src
└── main
│ ├── java
│ └── com
│ │ └── granveaud
│ │ └── offheapbench
│ │ ├── tests
│ │ ├── Test.java
│ │ ├── InsertTest.java
│ │ ├── AbstractTest.java
│ │ ├── SequentialReadTest.java
│ │ ├── RandomReadTest.java
│ │ ├── GaussianRandomReadTest.java
│ │ └── RandomReadWriteTest.java
│ │ ├── store
│ │ ├── OffHeapStore.java
│ │ ├── HeapStore.java
│ │ ├── FSTStore.java
│ │ ├── DirectMapStore.java
│ │ ├── MapDBStore.java
│ │ ├── JNAStore.java
│ │ ├── BigMemoryGoStore.java
│ │ ├── DirectMemoryStore.java
│ │ └── ChronicleStore.java
│ │ ├── utils
│ │ ├── StringUtils.java
│ │ └── FileUtils.java
│ │ ├── Main.java
│ │ └── bean
│ │ └── Bean.java
│ └── resources
│ ├── logback.xml
│ └── terracotta-license.key
├── README.md
├── LICENSE
└── pom.xml
/src/main/java/com/granveaud/offheapbench/tests/Test.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.store.OffHeapStore;
4 |
5 | public interface Test {
6 | void setUp(OffHeapStore store);
7 |
8 | void run();
9 |
10 | void cleanUp();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/OffHeapStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import java.io.Serializable;
4 |
5 | public interface OffHeapStore {
6 | void put(Serializable key, Serializable value);
7 |
8 | Object get(Serializable key);
9 |
10 | void remove(Serializable key);
11 |
12 | int size();
13 |
14 | void close();
15 |
16 | void displayStats();
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/tests/InsertTest.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 |
5 | public class InsertTest extends AbstractTest {
6 | @Override
7 | public void run() {
8 | for (int i = 0; i < NB_RECORDS; i++) {
9 | Bean bean = new Bean(10, 5);
10 | store.put(i, bean);
11 | }
12 |
13 | store.displayStats();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/tests/AbstractTest.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.store.OffHeapStore;
4 |
5 | public abstract class AbstractTest implements Test {
6 | final static public int NB_RECORDS = 500000;
7 |
8 | protected OffHeapStore store;
9 |
10 | @Override
11 | public void setUp(OffHeapStore store) {
12 | this.store = store;
13 | }
14 |
15 | @Override
16 | public void cleanUp() {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.utils;
2 |
3 | import java.nio.charset.Charset;
4 |
5 | public class StringUtils {
6 | private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
7 |
8 | public static byte[] stringToUTF8Bytes(String str) {
9 | if (str == null) return null;
10 |
11 | return str.getBytes(UTF8_CHARSET);
12 | }
13 |
14 | public static String utf8BytesToString(byte[] bytes) {
15 | if (bytes == null) return null;
16 |
17 | return new String(bytes, UTF8_CHARSET);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/terracotta-license.key:
--------------------------------------------------------------------------------
1 | # This software license, granted by Terracotta Inc. - a Software AG company, is valid for and may only be used
2 | # by the licensee of this product ("Licensee").
3 | # Descriptor version: 3
4 | # ------------------------------------------------------------------------------------
5 | Date of Issue: 2013-10-12
6 | License Type: Free Production
7 | License Number: 1006372
8 | Licensee: dfg dfg, jhkl
9 | Email: sdfsdfg@granveaud.com
10 | Product: BigMemory Go
11 | Edition: Free
12 | Capabilities: ehcache, TMC, ehcache offheap
13 | Max Client Count: 0
14 | ehcache.maxOffHeap: 32G
15 |
16 | Signature: MCwCFDJrf++XICYBXBZVM0kIjvGgvt7EAhRpUdbPu8o5KtwngZgliiSI0E6I4w==
17 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/tests/SequentialReadTest.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import com.granveaud.offheapbench.store.OffHeapStore;
5 |
6 | public class SequentialReadTest extends AbstractTest {
7 |
8 | @Override
9 | public void setUp(OffHeapStore store) {
10 | super.setUp(store);
11 |
12 | for (int i = 0; i < NB_RECORDS; i++) {
13 | Bean bean = new Bean(10, 5);
14 | store.put(i, bean);
15 | }
16 | store.displayStats();
17 | }
18 |
19 | @Override
20 | public void run() {
21 | for (int i = 0; i < NB_RECORDS; i++) {
22 | Bean bean = (Bean) store.get(i);
23 |
24 | if (bean.hashCode() != bean.getHashValue()) {
25 | throw new RuntimeException("hashValue is different");
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/tests/RandomReadTest.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import com.granveaud.offheapbench.store.OffHeapStore;
5 |
6 | import java.util.Random;
7 |
8 | public class RandomReadTest extends AbstractTest {
9 | private Random rand = new Random();
10 |
11 | @Override
12 | public void setUp(OffHeapStore store) {
13 | super.setUp(store);
14 |
15 | for (int i = 0; i < NB_RECORDS; i++) {
16 | Bean bean = new Bean(10, 5);
17 | store.put(i, bean);
18 | }
19 | store.displayStats();
20 | }
21 |
22 | @Override
23 | public void run() {
24 | for (int i = 0; i < NB_RECORDS; i++) {
25 | Bean bean = (Bean) store.get(rand.nextInt(NB_RECORDS));
26 |
27 | if (bean.hashCode() != bean.getHashValue()) {
28 | throw new RuntimeException("hashValue is different");
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | offheapstore-benchmark
2 | ======================
3 |
4 | A simple benchmark of some off heap solutions:
5 |
6 | - BigMemory Go (http://terracotta.org/products/bigmemorygo)
7 | - MapDB (http://www.mapdb.org/)
8 | - FSTOffHeapMap (https://code.google.com/p/fast-serialization/)
9 | - a Java Chronicle based map (https://github.com/OpenHFT/Java-Chronicle/)
10 | - a simple JNA based map (https://jna.java.net/)
11 | - an Apache DirectMemory based map (http://directmemory.apache.org/)
12 | - my own implementation: DirectMap (https://github.com/bgranvea/directobjects)
13 |
14 | We also test a regular heap map to measure the overhead of off heap implementations.
15 |
16 | We test different scenarios:
17 |
18 | - inserts
19 | - a sequential read of all entries
20 | - random reads
21 | - random reads with a gaussian function so that some elements are more likely to be accessed than others (should improve
22 | performances of implementations like Big Memory Go which uses a heap cache)
23 | - random reads and updates
24 |
25 | TODO:
26 | =====
27 | - test with different bean size
28 | - measure memory fragmentation in the read/update scenario
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Boris Granveaud
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/HeapStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.io.Serializable;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | public class HeapStore implements OffHeapStore {
11 | final static private Logger LOGGER = LoggerFactory.getLogger(HeapStore.class);
12 |
13 | private Map map;
14 |
15 | public HeapStore() {
16 | map = new HashMap();
17 | }
18 |
19 | @Override
20 | public void close() {
21 | map.clear();
22 | }
23 |
24 | @Override
25 | public void put(Serializable key, Serializable value) {
26 | map.put(key, value);
27 | }
28 |
29 | @Override
30 | public Object get(Serializable key) {
31 | return map.get(key);
32 | }
33 |
34 | @Override
35 | public void remove(Serializable key) {
36 | map.remove(key);
37 | }
38 |
39 | @Override
40 | public int size() {
41 | return map.size();
42 | }
43 |
44 | @Override
45 | public void displayStats() {
46 | LOGGER.info("Stats: size=" + map.size() + " entries");
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/tests/GaussianRandomReadTest.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import com.granveaud.offheapbench.store.OffHeapStore;
5 |
6 | import java.util.Random;
7 |
8 | public class GaussianRandomReadTest extends AbstractTest {
9 |
10 | private Random rand = new Random();
11 |
12 | @Override
13 | public void setUp(OffHeapStore store) {
14 | super.setUp(store);
15 |
16 | for (int i = 0; i < NB_RECORDS; i++) {
17 | Bean bean = new Bean(10, 5);
18 | store.put(i, bean);
19 | }
20 | store.displayStats();
21 | }
22 |
23 | @Override
24 | public void run() {
25 | int halfNbRecords = NB_RECORDS / 2;
26 | for (int i = 0; i < NB_RECORDS; i++) {
27 | // note: index can be out of range because of the nextGaussian function
28 | double gaussian = rand.nextGaussian() / 2;
29 | int index = halfNbRecords + (int) (halfNbRecords * gaussian);
30 |
31 | Bean bean = (Bean) store.get(index);
32 |
33 | if (bean != null && bean.hashCode() != bean.getHashValue()) {
34 | throw new RuntimeException("hashValue is different");
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/tests/RandomReadWriteTest.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.tests;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import com.granveaud.offheapbench.store.OffHeapStore;
5 |
6 | import java.util.Random;
7 |
8 | public class RandomReadWriteTest extends AbstractTest {
9 | private Random rand = new Random();
10 |
11 | @Override
12 | public void setUp(OffHeapStore store) {
13 | super.setUp(store);
14 |
15 | for (int i = 0; i < NB_RECORDS; i++) {
16 | Bean bean = new Bean(10, 5);
17 | store.put(i, bean);
18 | }
19 | store.displayStats();
20 | }
21 |
22 | @Override
23 | public void run() {
24 | for (int i = 0; i < NB_RECORDS; i++) {
25 | if (rand.nextBoolean()) {
26 | // replace random bean
27 | int index = rand.nextInt(NB_RECORDS);
28 | Bean bean = new Bean(10, 5);
29 | store.put(index, bean);
30 | } else {
31 | // read a random bean
32 | Bean bean = (Bean) store.get(rand.nextInt(NB_RECORDS));
33 |
34 | if (bean.hashCode() != bean.getHashValue()) {
35 | throw new RuntimeException("hashValue is different");
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.utils;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | public class FileUtils {
10 | final static private Logger LOGGER = LoggerFactory.getLogger(FileUtils.class);
11 | final static private File TEMP_DIR = new File(System.getProperty("java.io.tmpdir"), "offheapbench");
12 |
13 | static {
14 | // delete previous directory
15 | if (TEMP_DIR.exists()) {
16 | LOGGER.info("Deleting temp directory " + TEMP_DIR.getAbsolutePath());
17 | deleteDir(TEMP_DIR);
18 | }
19 |
20 | // create a temp directory
21 | if (!TEMP_DIR.mkdirs()) {
22 | throw new RuntimeException("Can't create " + TEMP_DIR.getAbsolutePath());
23 | }
24 | }
25 |
26 | private static void deleteDir(File f) {
27 | if (f.isDirectory()) {
28 | for (File c : f.listFiles())
29 | deleteDir(c);
30 | }
31 |
32 | if (!f.delete()) {
33 | throw new RuntimeException("Failed to delete file: " + f);
34 | }
35 | }
36 |
37 | public static File createTempFile() throws IOException {
38 | return File.createTempFile("offheapbench", null, TEMP_DIR);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/FSTStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import de.ruedigermoeller.heapoff.FSTOffHeapMap;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.io.IOException;
8 | import java.io.Serializable;
9 |
10 | public class FSTStore implements OffHeapStore {
11 | final static private Logger LOGGER = LoggerFactory.getLogger(FSTStore.class);
12 |
13 | private FSTOffHeapMap map;
14 |
15 | public FSTStore() {
16 | try {
17 | map = new FSTOffHeapMap(800);
18 | } catch (IOException e) {
19 | throw new RuntimeException("Cannot create FSTOffHeapMap", e);
20 | }
21 | }
22 |
23 | @Override
24 | public void close() {
25 | map.clear();
26 | }
27 |
28 | @Override
29 | public void put(Serializable key, Serializable value) {
30 | map.put(key, value);
31 | }
32 |
33 | @Override
34 | public Object get(Serializable key) {
35 | return map.get(key);
36 | }
37 |
38 | @Override
39 | public void remove(Serializable key) {
40 | map.remove(key);
41 | }
42 |
43 | @Override
44 | public int size() {
45 | return map.size();
46 | }
47 |
48 | @Override
49 | public void displayStats() {
50 | LOGGER.info("Stats: size=" + map.size() + " entries");
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/DirectMapStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import com.granveaud.directobjects.DirectObject;
4 | import com.granveaud.directobjects.map.DirectMap;
5 | import com.granveaud.offheapbench.bean.Bean;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.io.Serializable;
10 |
11 | public class DirectMapStore implements OffHeapStore {
12 | final static private Logger LOGGER = LoggerFactory.getLogger(DirectMapStore.class);
13 |
14 | private DirectMap map;
15 |
16 | public DirectMapStore() {
17 | map = new DirectMap();
18 | }
19 |
20 | @Override
21 | public void close() {
22 | map.clear();
23 | }
24 |
25 | @Override
26 | public void put(Serializable key, Serializable value) {
27 | map.put(key, (DirectObject) value);
28 | }
29 |
30 | @Override
31 | public Object get(Serializable key) {
32 | Bean bean = new Bean(); // warning: we suppose here that values are Bean
33 | if (map.get(key, bean)) {
34 | return bean;
35 | }
36 | return null;
37 | }
38 |
39 | @Override
40 | public void remove(Serializable key) {
41 | map.remove(key);
42 | }
43 |
44 | @Override
45 | public int size() {
46 | return map.size();
47 | }
48 |
49 | @Override
50 | public void displayStats() {
51 | LOGGER.info("Stats: size=" + map.size() + " entries");
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/MapDBStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import com.granveaud.offheapbench.utils.FileUtils;
4 | import org.mapdb.BTreeMap;
5 | import org.mapdb.DB;
6 | import org.mapdb.DBMaker;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.io.Serializable;
13 |
14 | public class MapDBStore implements OffHeapStore {
15 | final static private Logger LOGGER = LoggerFactory.getLogger(MapDBStore.class);
16 |
17 | private DB db;
18 | private BTreeMap map;
19 |
20 | public MapDBStore() throws IOException {
21 | File dbFile = FileUtils.createTempFile();
22 | LOGGER.info("Using temp file " + dbFile);
23 | db = DBMaker
24 | .newFileDB(dbFile)
25 | .deleteFilesAfterClose()
26 | .transactionDisable()
27 | // .asyncWriteDisable()
28 | .make();
29 |
30 | map = db.createTreeMap("map")
31 | .make();
32 | }
33 |
34 | @Override
35 | public void close() {
36 | LOGGER.info("Size=" + map.size()); // force sync
37 |
38 | map.close();
39 | db.close();
40 | }
41 |
42 | @Override
43 | public void put(Serializable key, Serializable value) {
44 | map.put(key, value);
45 | }
46 |
47 | @Override
48 | public Object get(Serializable key) {
49 | return map.get(key);
50 | }
51 |
52 | @Override
53 | public void remove(Serializable key) {
54 | map.remove(key);
55 | }
56 |
57 | @Override
58 | public int size() {
59 | return map.size();
60 | }
61 |
62 | @Override
63 | public void displayStats() {
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/JNAStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import com.sun.jna.Memory;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.io.*;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | public class JNAStore implements OffHeapStore {
13 | final static private Logger LOGGER = LoggerFactory.getLogger(JNAStore.class);
14 |
15 | // Note: Memory.finalize will free native memory if an element is replaced/removed
16 | private Map memoryMap;
17 | private long allocated;
18 |
19 | public JNAStore() {
20 | memoryMap = new HashMap();
21 | }
22 |
23 | @Override
24 | public void close() {
25 | memoryMap.clear();
26 |
27 | // Note: Memory.finalize will free native memory
28 | }
29 |
30 | @Override
31 | public void put(Serializable key, Serializable value) {
32 | Bean b = (Bean) value;
33 |
34 | Memory m = new Memory(b.getJNALength());
35 | b.write(m);
36 |
37 | memoryMap.put(key, m);
38 |
39 | allocated += m.size();
40 | }
41 |
42 | @Override
43 | public Object get(Serializable key) {
44 | Memory m = memoryMap.get(key);
45 | if (m == null) return null;
46 |
47 | Bean b = new Bean();
48 | b.read(m);
49 |
50 | return b;
51 | }
52 |
53 | @Override
54 | public void remove(Serializable key) {
55 | memoryMap.remove(key);
56 | }
57 |
58 | @Override
59 | public int size() {
60 | return memoryMap.size();
61 | }
62 |
63 | @Override
64 | public void displayStats() {
65 | LOGGER.info("Stats: size=" + memoryMap.size() + " entries / allocated=" + allocated);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/BigMemoryGoStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import net.sf.ehcache.Cache;
4 | import net.sf.ehcache.CacheManager;
5 | import net.sf.ehcache.Ehcache;
6 | import net.sf.ehcache.Element;
7 | import net.sf.ehcache.config.CacheConfiguration;
8 | import net.sf.ehcache.config.Configuration;
9 | import net.sf.ehcache.config.MemoryUnit;
10 | import net.sf.ehcache.event.CacheEventListener;
11 | import net.sf.ehcache.event.CacheEventListenerAdapter;
12 | import net.sf.ehcache.statistics.StatisticsGateway;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | import java.io.Serializable;
17 |
18 | public class BigMemoryGoStore implements OffHeapStore {
19 | final static private Logger LOGGER = LoggerFactory.getLogger(BigMemoryGoStore.class);
20 |
21 | private CacheManager cacheManager;
22 | private Cache cache;
23 | private boolean cacheFull;
24 |
25 | public BigMemoryGoStore() {
26 | Configuration managerConfiguration = new Configuration()
27 | .name("benchmark")
28 | .cache(new CacheConfiguration()
29 | .name("store")
30 | .maxBytesLocalHeap(50, MemoryUnit.MEGABYTES)
31 | .maxBytesLocalOffHeap(500, MemoryUnit.MEGABYTES)
32 | .eternal(true)
33 | );
34 |
35 | cacheManager = CacheManager.create(managerConfiguration);
36 | cache = cacheManager.getCache("store");
37 |
38 | // get notified when cache is not big enough
39 | CacheEventListener evictionListener = new CacheEventListenerAdapter() {
40 | @Override
41 | public void notifyElementEvicted(Ehcache ehcache, Element element) {
42 | cacheFull = true;
43 | }
44 | };
45 | cache.getCacheEventNotificationService().registerListener(evictionListener);
46 | }
47 |
48 | @Override
49 | public void close() {
50 | LOGGER.info("CLOSE STORE");
51 | if (cacheManager != null) {
52 | cacheManager.shutdown();
53 | }
54 | LOGGER.info("CLOSE STORE OK");
55 | }
56 |
57 | @Override
58 | public void put(Serializable key, Serializable value) {
59 | cache.put(new Element(key, value));
60 |
61 | if (cacheFull) {
62 | throw new RuntimeException("Cache store is full!");
63 | }
64 | }
65 |
66 | @Override
67 | public Object get(Serializable key) {
68 | Element elt = cache.get(key);
69 | return (elt != null ? elt.getObjectValue() : null);
70 | }
71 |
72 | @Override
73 | public void remove(Serializable key) {
74 | cache.remove(key);
75 | }
76 |
77 | @Override
78 | public int size() {
79 | return cache.getSize();
80 | }
81 |
82 | @Override
83 | public void displayStats() {
84 | StatisticsGateway s = cache.getStatistics();
85 | LOGGER.info("Stats: size=" + s.getSize() + " localHeap=" + s.getLocalHeapSize() + " entries/" + s.getLocalHeapSizeInBytes()
86 | + " bytes localOffHeap=" + s.getLocalOffHeapSize() + " entries/" + s.getLocalOffHeapSizeInBytes() + " bytes");
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/DirectMemoryStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import de.ruedigermoeller.serialization.FSTConfiguration;
5 | import de.ruedigermoeller.serialization.FSTObjectInput;
6 | import de.ruedigermoeller.serialization.FSTObjectOutput;
7 | import org.apache.directmemory.DirectMemory;
8 | import org.apache.directmemory.cache.CacheService;
9 | import org.apache.directmemory.serialization.Serializer;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.io.ByteArrayOutputStream;
14 | import java.io.IOException;
15 | import java.io.Serializable;
16 |
17 | public class DirectMemoryStore implements OffHeapStore {
18 | final static private Logger LOGGER = LoggerFactory.getLogger(DirectMemoryStore.class);
19 |
20 | final static private Serializer FST_SERIALIZER = new Serializer() {
21 | private FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();
22 |
23 | @Override
24 | public byte[] serialize(T obj) throws IOException {
25 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
26 | FSTObjectOutput out = conf.getObjectOutput(bos);
27 | out.writeObject( obj, Bean.class );
28 | out.flush();
29 |
30 | return bos.toByteArray();
31 | }
32 |
33 | @Override
34 | @SuppressWarnings("unchecked")
35 | public T deserialize(byte[] source, Class clazz) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
36 | FSTObjectInput in = conf.getObjectInput(source, 0, source.length);
37 | try {
38 | return (T) in.readObject(clazz);
39 | } catch (Exception e) {
40 | throw new IOException("Cannot read object", e);
41 | }
42 | }
43 | };
44 |
45 | private CacheService cacheService;
46 |
47 | public DirectMemoryStore() {
48 | cacheService = new DirectMemory()
49 | .setNumberOfBuffers(60)
50 | .setSize(10000000)
51 | .setInitialCapacity(1000000)
52 | .setConcurrencyLevel(2)
53 | .setSerializer(FST_SERIALIZER)
54 | .newCacheService();
55 | }
56 |
57 | @Override
58 | public void close() {
59 | cacheService.clear();
60 | try {
61 | cacheService.close();
62 | } catch (IOException e) {
63 | LOGGER.warn("Exception during close", e);
64 | }
65 | }
66 |
67 | @Override
68 | public void put(Serializable key, Serializable value) {
69 | cacheService.put(key, value);
70 | }
71 |
72 | @Override
73 | public Object get(Serializable key) {
74 | return cacheService.retrieve(key);
75 | }
76 |
77 | @Override
78 | public void remove(Serializable key) {
79 | cacheService.free(key);
80 | }
81 |
82 | @Override
83 | public int size() {
84 | return (int) cacheService.entries();
85 | }
86 |
87 | @Override
88 | public void displayStats() {
89 | LOGGER.info("Stats: size=" + size() + " entries");
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.granveaud
8 | offheapbench
9 | 0.1-SNAPSHOT
10 |
11 |
12 |
13 | org.apache.directmemory
14 | directmemory-cache
15 | 0.2
16 |
17 |
18 | org.terracotta.bigmemory
19 | bigmemory
20 | 4.0.4
21 |
22 |
23 | net.sf.ehcache
24 | ehcache-ee
25 | 2.7.4
26 |
27 |
28 | org.mapdb
29 | mapdb
30 | 0.9.6
31 |
32 |
33 | net.openhft
34 | chronicle
35 | 2.0.1
36 |
37 |
38 | net.sf.trove4j
39 | trove4j
40 | 3.0.3
41 |
42 |
43 | ch.qos.logback
44 | logback-classic
45 | 1.0.13
46 |
47 |
48 | com.codahale.metrics
49 | metrics-core
50 | 3.0.1
51 |
52 |
53 | com.google.guava
54 | guava
55 | 15.0
56 |
57 |
58 | net.java.dev.jna
59 | jna
60 | 4.0.0
61 |
62 |
63 | de.ruedigermoeller
64 | fst
65 | 1.27
66 |
67 |
68 | com.granveaud
69 | directobjects
70 | 0.1-SNAPSHOT
71 |
72 |
73 | junit
74 | junit
75 | 4.11
76 | test
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | org.apache.maven.plugins
85 | maven-shade-plugin
86 | 2.1
87 |
88 |
89 | package
90 |
91 | shade
92 |
93 |
94 |
95 |
97 | com.granveaud.offheapbench.Main
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/Main.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench;
2 |
3 | import com.codahale.metrics.Histogram;
4 | import com.codahale.metrics.MetricRegistry;
5 | import com.codahale.metrics.Snapshot;
6 | import com.google.common.collect.Lists;
7 | import com.google.common.io.Files;
8 | import com.granveaud.offheapbench.store.*;
9 | import com.granveaud.offheapbench.tests.*;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.nio.charset.Charset;
16 | import java.util.List;
17 |
18 | public class Main {
19 | final static private Logger LOGGER = LoggerFactory.getLogger(Main.class);
20 |
21 | final static private int NB_WARMUPS = 1;
22 | final static private int NB_RUNS = 2;
23 |
24 | @SuppressWarnings("unchecked")
25 | final static private List> STORE_CLASSES = Lists.>newArrayList(
26 | HeapStore.class,
27 | BigMemoryGoStore.class,
28 | MapDBStore.class,
29 | ChronicleStore.class,
30 | JNAStore.class,
31 | FSTStore.class,
32 | DirectMapStore.class,
33 | DirectMemoryStore.class
34 | );
35 |
36 | @SuppressWarnings("unchecked")
37 | final static private List> TEST_CLASSES = Lists.>newArrayList(
38 | InsertTest.class,
39 | SequentialReadTest.class,
40 | RandomReadTest.class,
41 | GaussianRandomReadTest.class,
42 | RandomReadWriteTest.class
43 | );
44 |
45 | final static private MetricRegistry metrics = new MetricRegistry();
46 |
47 | final static private File OUTPUT_CSV = new File("results.csv");
48 | final static private Charset UTF8 = Charset.forName("UTF8");
49 |
50 | public static void main(String[] args) throws IOException {
51 | Files.write("test\tstore\tcount\tmin\tmax\tmean\tmedian\tp75\tp95\tp98\t99\tstatus\n", OUTPUT_CSV, UTF8);
52 |
53 | for (Class extends Test> t : TEST_CLASSES) {
54 | for (Class extends OffHeapStore> s : STORE_CLASSES) {
55 | runTest(t, s);
56 | }
57 | }
58 | }
59 |
60 | private static void runTest(Class extends Test> testClass, Class extends OffHeapStore> storeClass) throws IOException {
61 | Histogram histo = metrics.histogram("times-" + testClass.getSimpleName() + "-" + storeClass);
62 |
63 | Throwable throwable = null;
64 | OffHeapStore store = null;
65 | try {
66 | for (int i = -NB_WARMUPS; i < NB_RUNS; i++) {
67 | gc();
68 |
69 | LOGGER.info((i < 0 ? "Warmup..." : "Run " + i));
70 |
71 | LOGGER.info("Preparing " + testClass.getSimpleName() + " with store " + storeClass.getSimpleName());
72 |
73 | Test test = testClass.newInstance();
74 | store = storeClass.newInstance();
75 |
76 | test.setUp(store);
77 |
78 | LOGGER.info("Running " + testClass.getSimpleName() + " with store " + storeClass.getSimpleName());
79 |
80 | long time0 = System.currentTimeMillis();
81 | test.run();
82 | histo.update(System.currentTimeMillis() - time0);
83 |
84 | LOGGER.info("Finishing " + testClass.getSimpleName() + " with store " + storeClass.getSimpleName());
85 | test.cleanUp();
86 | }
87 | } catch (Throwable t) {
88 | throwable = t;
89 | LOGGER.info("Error during test", t);
90 | } finally {
91 | // always cleanup store
92 | try {
93 | store.close();
94 | } catch (Throwable t2) {
95 | }
96 | }
97 |
98 | displayResults(testClass, storeClass, histo, throwable);
99 | saveResults(testClass, storeClass, histo, throwable);
100 | }
101 |
102 | private static void gc() {
103 | LOGGER.info("Calling GC and sleeping 10s");
104 | System.gc();
105 | try {
106 | Thread.sleep(10000);
107 | } catch (InterruptedException e) {
108 | }
109 | }
110 |
111 | private static void displayResults(Class extends Test> testClass, Class extends OffHeapStore> storeClass, Histogram histo, Throwable t) throws IOException {
112 | Snapshot snapshot = histo.getSnapshot();
113 |
114 | LOGGER.info(
115 | String.format(
116 | "Results: %s %s count=%d min=%f max=%f mean=%f median=%f 75p=%f 95p=%f 98p=%f 99p=%f status=%s",
117 | testClass.getSimpleName(),
118 | storeClass.getSimpleName(),
119 | histo.getCount(),
120 | snapshot.getMin() / 1000.0,
121 | snapshot.getMax() / 1000.0,
122 | snapshot.getMean() / 1000.0,
123 | snapshot.getMedian() / 1000.0,
124 | snapshot.get75thPercentile() / 1000.0,
125 | snapshot.get95thPercentile() / 1000.0,
126 | snapshot.get98thPercentile() / 1000.0,
127 | snapshot.get99thPercentile() / 1000.0,
128 | t != null ? t.getMessage() : "OK"
129 | )
130 | );
131 | }
132 |
133 | private static void saveResults(Class extends Test> testClass, Class extends OffHeapStore> storeClass, Histogram histo, Throwable t) throws IOException {
134 | Snapshot snapshot = histo.getSnapshot();
135 |
136 | Files.append(
137 | String.format(
138 | "%s\t%s\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%s\n",
139 | testClass.getSimpleName(),
140 | storeClass.getSimpleName(),
141 | histo.getCount(),
142 | snapshot.getMin() / 1000.0,
143 | snapshot.getMax() / 1000.0,
144 | snapshot.getMean() / 1000.0,
145 | snapshot.getMedian() / 1000.0,
146 | snapshot.get75thPercentile() / 1000.0,
147 | snapshot.get95thPercentile() / 1000.0,
148 | snapshot.get98thPercentile() / 1000.0,
149 | snapshot.get99thPercentile() / 1000.0,
150 | t != null ? t.getMessage() : "OK"
151 | ),
152 | OUTPUT_CSV,
153 | UTF8);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/store/ChronicleStore.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.store;
2 |
3 | import com.granveaud.offheapbench.bean.Bean;
4 | import com.granveaud.offheapbench.utils.FileUtils;
5 | import gnu.trove.impl.Constants;
6 | import gnu.trove.iterator.TIntObjectIterator;
7 | import gnu.trove.iterator.TLongLongIterator;
8 | import gnu.trove.list.TLongList;
9 | import gnu.trove.list.array.TLongArrayList;
10 | import gnu.trove.map.TIntObjectMap;
11 | import gnu.trove.map.TLongIntMap;
12 | import gnu.trove.map.TLongLongMap;
13 | import gnu.trove.map.TObjectLongMap;
14 | import gnu.trove.map.hash.TIntObjectHashMap;
15 | import gnu.trove.map.hash.TLongIntHashMap;
16 | import gnu.trove.map.hash.TLongLongHashMap;
17 | import gnu.trove.map.hash.TObjectLongHashMap;
18 | import gnu.trove.procedure.TIntObjectProcedure;
19 | import gnu.trove.procedure.TIntProcedure;
20 | import net.openhft.chronicle.Chronicle;
21 | import net.openhft.chronicle.Excerpt;
22 | import net.openhft.chronicle.ExcerptAppender;
23 | import net.openhft.chronicle.IndexedChronicle;
24 | import net.openhft.lang.io.IOTools;
25 | import org.slf4j.Logger;
26 | import org.slf4j.LoggerFactory;
27 |
28 | import java.io.File;
29 | import java.io.IOException;
30 | import java.io.Serializable;
31 | import java.util.Map;
32 | import java.util.TreeMap;
33 |
34 | public class ChronicleStore implements OffHeapStore {
35 | final static private Logger LOGGER = LoggerFactory.getLogger(ChronicleStore.class);
36 |
37 | final static private int NO_ENTRY = -1;
38 |
39 | final static private int CHUNK_SIZE = 32;
40 |
41 | private File file;
42 | private Chronicle chronicle;
43 | private Excerpt randomAccessor;
44 | private ExcerptAppender appender;
45 |
46 | private TObjectLongMap posMap; // key => excerpt position
47 | private TLongIntMap posSizeMap; // excerpt position => size in chunks (for used excerpts)
48 | private TIntObjectMap freePosSizeMap; // size in chunks => list of free excerpt position
49 | private int freePosMaxSize; // max size in chunks contained in freePosSizeMap
50 |
51 | public ChronicleStore() throws IOException {
52 | file = FileUtils.createTempFile();
53 |
54 | LOGGER.info("Using " + file.getAbsolutePath());
55 |
56 | chronicle = new IndexedChronicle(file.getAbsolutePath());
57 | posMap = new TObjectLongHashMap(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, NO_ENTRY);
58 | posSizeMap = new TLongIntHashMap(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, NO_ENTRY, NO_ENTRY);
59 | freePosSizeMap = new TIntObjectHashMap(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, NO_ENTRY);
60 | freePosMaxSize = 0;
61 |
62 | appender = chronicle.createAppender();
63 | randomAccessor = chronicle.createExcerpt();
64 | }
65 |
66 | @Override
67 | public void close() {
68 | IOTools.close(chronicle);
69 | file.delete();
70 | }
71 |
72 | @Override
73 | public void put(Serializable key, Serializable value) {
74 | Bean b = (Bean) value;
75 | long beanSize = 4 + b.getChronicleLength();
76 | int nbChunks = (int) ((beanSize - 1) / CHUNK_SIZE) + 1;
77 | int paddedSize = nbChunks * CHUNK_SIZE;
78 |
79 | if (posMap.containsKey(key)) {
80 | // free existing entry
81 | remove(key);
82 | }
83 |
84 | // if possible, try to reuse existing free entry
85 | if (!freePosSizeMap.isEmpty() && putInFreePosition(key, value, nbChunks)) {
86 | return;
87 | }
88 |
89 | // append new excerpt
90 | appender.startExcerpt(paddedSize);
91 | b.write(appender);
92 | appender.position(paddedSize); // padding
93 | appender.finish();
94 |
95 | // update maps
96 | long pos = appender.lastWrittenIndex();
97 | posMap.put(key, pos);
98 | posSizeMap.put(pos, nbChunks);
99 | }
100 |
101 | private boolean putInFreePosition(Serializable key, Serializable value, int nbChunks) {
102 | Bean b = (Bean) value;
103 |
104 | // find the smallest block big enough
105 | int i = nbChunks;
106 | while (i <= freePosMaxSize) {
107 | TLongList posList = freePosSizeMap.get(i);
108 | if (posList == null) {
109 | i++;
110 | continue;
111 | }
112 |
113 | // take last position of list (should be faster)
114 | long pos = posList.removeAt(posList.size() - 1);
115 |
116 | // remove list if empty
117 | if (posList.isEmpty()) {
118 | freePosSizeMap.remove(i);
119 | determineFreePosMaxSize();
120 | }
121 |
122 | // update existing entry
123 | randomAccessor.index(pos);
124 | b.write(randomAccessor);
125 | randomAccessor.position(nbChunks * CHUNK_SIZE); // padding
126 | randomAccessor.finish();
127 |
128 | // update maps
129 | posMap.put(key, pos);
130 | posSizeMap.put(pos, nbChunks);
131 |
132 | return true;
133 | }
134 |
135 | return false;
136 | }
137 |
138 | private void determineFreePosMaxSize() {
139 | freePosMaxSize = 0;
140 |
141 | freePosSizeMap.forEachKey(new TIntProcedure() {
142 | @Override
143 | public boolean execute(int size) {
144 | if (size > freePosMaxSize) freePosMaxSize = size;
145 | return true;
146 | }
147 | });
148 | }
149 |
150 | @Override
151 | public Object get(Serializable key) {
152 | long pos = posMap.get(key);
153 | if (pos == NO_ENTRY) return null;
154 |
155 | Bean b = new Bean();
156 |
157 | // read
158 | randomAccessor.index(pos);
159 | b.read(randomAccessor);
160 | randomAccessor.finish();
161 |
162 | return b;
163 | }
164 |
165 | @Override
166 | public void remove(Serializable key) {
167 | long pos = posMap.get(key);
168 | if (pos == NO_ENTRY) return;
169 |
170 | // update maps
171 | int nbChunks = posSizeMap.get(pos);
172 | posMap.remove(pos);
173 | posSizeMap.remove(pos);
174 |
175 | TLongList posList = freePosSizeMap.get(nbChunks);
176 | if (posList == null) {
177 | posList = new TLongArrayList(Constants.DEFAULT_CAPACITY, NO_ENTRY);
178 | freePosSizeMap.put(nbChunks, posList);
179 | }
180 |
181 | posList.add(pos);
182 |
183 | if (nbChunks > freePosMaxSize) freePosMaxSize = nbChunks;
184 | }
185 |
186 | @Override
187 | public int size() {
188 | return posMap.size();
189 | }
190 |
191 | private int getNbFreeChunks() {
192 | int res = 0;
193 |
194 | TIntObjectIterator it = freePosSizeMap.iterator();
195 | while (it.hasNext()) {
196 | it.advance();
197 | res += it.key() * it.value().size();
198 | }
199 |
200 | return res;
201 | }
202 |
203 | @Override
204 | public void displayStats() {
205 | LOGGER.info("Stats: size=" + posMap.size() + " entries / " + appender.lastWrittenIndex() + " chronicle bytes");
206 | LOGGER.info("Stats: freeChunks={}", getNbFreeChunks());
207 | LOGGER.info("Stats: freePosMaxSize={}", freePosMaxSize);
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/main/java/com/granveaud/offheapbench/bean/Bean.java:
--------------------------------------------------------------------------------
1 | package com.granveaud.offheapbench.bean;
2 |
3 | import com.granveaud.directobjects.DirectObject;
4 | import com.granveaud.directobjects.DirectObjectContext;
5 | import com.granveaud.offheapbench.utils.StringUtils;
6 | import com.sun.jna.Memory;
7 | import net.openhft.lang.io.Bytes;
8 |
9 | import java.io.Externalizable;
10 | import java.io.IOException;
11 | import java.io.ObjectInput;
12 | import java.io.ObjectOutput;
13 | import java.util.Arrays;
14 | import java.util.Random;
15 |
16 | public class Bean implements Externalizable, DirectObject {
17 | final static private Random rand = new Random();
18 |
19 | private int value1;
20 | private String value2;
21 |
22 | private int[] array1;
23 | private String[] array2;
24 |
25 | // value2 and array2 strings are converted to byte[] so that we know serialization length in advance
26 | private byte[] value2Bytes;
27 | private byte[][] array2Bytes;
28 |
29 | private int hashValue;
30 |
31 | public Bean() {
32 | }
33 |
34 | // create a bean with random values
35 | public Bean(int maxStringLength, int maxArrayLength) {
36 | value1 = rand.nextInt();
37 | value2 = randomString(maxStringLength);
38 |
39 | array1 = new int[rand.nextInt(maxArrayLength) + 1];
40 | for (int i = 0; i < array1.length; i++) {
41 | array1[i] = rand.nextInt();
42 | }
43 |
44 | array2 = new String[rand.nextInt(maxArrayLength) + 1];
45 | for (int i = 0; i < array2.length; i++) {
46 | array2[i] = randomString(maxStringLength);
47 | }
48 |
49 | // prepare serialization
50 | value2Bytes = StringUtils.stringToUTF8Bytes(value2);
51 | array2Bytes = new byte[array2.length][];
52 | for (int i = 0; i < array2.length; i++) {
53 | array2Bytes[i] = StringUtils.stringToUTF8Bytes(array2[i]);
54 | }
55 | }
56 |
57 | private String randomString(int maxLength) {
58 | int length = rand.nextInt(maxLength) + 1;
59 | StringBuilder sb = new StringBuilder(length);
60 | for (int i = 0; i < length; i++) {
61 | sb.append((char) rand.nextInt(65536));
62 | }
63 |
64 | return sb.toString();
65 | }
66 |
67 | public int getValue1() {
68 | return value1;
69 | }
70 |
71 | public void setValue1(int value1) {
72 | this.value1 = value1;
73 | }
74 |
75 | public String getValue2() {
76 | return value2;
77 | }
78 |
79 | public void setValue2(String value2) {
80 | this.value2 = value2;
81 | }
82 |
83 | public int[] getArray1() {
84 | return array1;
85 | }
86 |
87 | public void setArray1(int[] array1) {
88 | this.array1 = array1;
89 | }
90 |
91 | public String[] getArray2() {
92 | return array2;
93 | }
94 |
95 | public void setArray2(String[] array2) {
96 | this.array2 = array2;
97 | }
98 |
99 | public static Random getRand() {
100 | return rand;
101 | }
102 |
103 | public int getHashValue() {
104 | return hashCode();
105 | }
106 |
107 | public void setHashValue(int hashValue) {
108 | this.hashValue = hashValue;
109 | }
110 |
111 | @Override
112 | public int hashCode() {
113 | int result = value1;
114 | result = 31 * result + (value2 != null ? value2.hashCode() : 0);
115 | result = 31 * result + (array1 != null ? Arrays.hashCode(array1) : 0);
116 | result = 31 * result + (array2 != null ? Arrays.hashCode(array2) : 0);
117 | return result;
118 | }
119 |
120 | /**
121 | * Serialization/deserialization
122 | */
123 |
124 | // Chronicle
125 | public int getChronicleLength() {
126 | int p = 0;
127 | p += 4; // value1
128 | p += 4 + value2Bytes.length; // value2
129 | p += 4 + 4 * array1.length; // array1
130 | p += 4; // array2.length;
131 | for (byte[] b : array2Bytes) {
132 | p += 4 + b.length;
133 | }
134 |
135 | return p;
136 | }
137 |
138 | public void write(Bytes out) {
139 | out.writeInt(value1);
140 | out.writeInt(value2Bytes.length);
141 | out.write(value2Bytes);
142 |
143 | out.writeInt(array1.length);
144 | for (int i = 0; i < array1.length; i++) {
145 | out.writeInt(array1[i]);
146 | }
147 |
148 | out.writeInt(array2Bytes.length);
149 | for (int i = 0; i < array2Bytes.length; i++) {
150 | out.writeInt(array2Bytes[i].length);
151 | out.write(array2Bytes[i]);
152 | }
153 |
154 | out.writeInt(getHashValue());
155 | }
156 |
157 | public void read(Bytes in) throws IllegalStateException {
158 | value1 = in.readInt();
159 | value2Bytes = new byte[in.readInt()];
160 | in.read(value2Bytes, 0, value2Bytes.length);
161 | value2 = StringUtils.utf8BytesToString(value2Bytes);
162 |
163 | array1 = new int[in.readInt()];
164 | for (int i = 0; i < array1.length; i++) {
165 | array1[i] = in.readInt();
166 | }
167 |
168 | array2Bytes = new byte[in.readInt()][];
169 | array2 = new String[array2Bytes.length];
170 | for (int i = 0; i < array2.length; i++) {
171 | array2Bytes[i] = new byte[in.readInt()];
172 | in.read(array2Bytes[i], 0, array2Bytes[i].length);
173 | array2[i] = StringUtils.utf8BytesToString(array2Bytes[i]);
174 | }
175 |
176 | hashValue = in.readInt();
177 | }
178 |
179 | // JNA
180 | public int getJNALength() {
181 | int p = 0;
182 | p += 4; // value1
183 | p += 4 + value2Bytes.length; // value2
184 | p += 4 + 4 * array1.length; // array1
185 | p += 4; // array2.length;
186 | for (byte[] b : array2Bytes) {
187 | p += 4 + b.length;
188 | }
189 |
190 | return p;
191 | }
192 |
193 | public void write(Memory m) {
194 | int p = 0;
195 | m.setInt(p, value1);
196 | p += 4;
197 | m.setInt(p, value2Bytes.length);
198 | p += 4;
199 | m.write(p, value2Bytes, 0, value2Bytes.length);
200 | p += value2Bytes.length;
201 | m.setInt(p, array1.length);
202 | p += 4;
203 | m.write(p, array1, 0, array1.length);
204 | p += array1.length * 4;
205 | m.setInt(p, array2Bytes.length);
206 | p += 4;
207 | for (byte[] b : array2Bytes) {
208 | m.setInt(p, b.length);
209 | p += 4;
210 | m.write(p, b, 0, b.length);
211 | p += b.length;
212 | }
213 | }
214 |
215 | public void read(Memory m) {
216 | int p = 0;
217 | value1 = m.getInt(p);
218 | p += 4;
219 | value2Bytes = new byte[m.getInt(p)];
220 | p += 4;
221 | m.read(p, value2Bytes, 0, value2Bytes.length);
222 | p += value2Bytes.length;
223 | value2 = StringUtils.utf8BytesToString(value2Bytes);
224 | array1 = new int[m.getInt(p)];
225 | p += 4;
226 | m.read(p, array1, 0, array1.length);
227 | p += array1.length * 4;
228 | array2Bytes = new byte[m.getInt(p)][];
229 | p += 4;
230 | array2 = new String[array2Bytes.length];
231 | for (int i = 0; i < array2Bytes.length; i++) {
232 | array2Bytes[i] = new byte[m.getInt(p)];
233 | p += 4;
234 | m.read(p, array2Bytes[i], 0, array2Bytes[i].length);
235 | p += array2Bytes[i].length;
236 | array2[i] = StringUtils.utf8BytesToString(array2Bytes[i]);
237 | }
238 | }
239 |
240 | // Externalizable
241 | @Override
242 | public void writeExternal(ObjectOutput oo) throws IOException {
243 | oo.writeInt(value1);
244 | oo.writeInt(value2Bytes.length);
245 | oo.write(value2Bytes);
246 | oo.writeInt(array1.length);
247 | for (int v : array1) {
248 | oo.writeInt(v);
249 | }
250 |
251 | oo.writeInt(array2Bytes.length);
252 | for (byte[] b : array2Bytes) {
253 | oo.writeInt(b.length);
254 | oo.write(b);
255 | }
256 | }
257 |
258 | @Override
259 | public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
260 | value1 = oi.readInt();
261 | value2Bytes = new byte[oi.readInt()];
262 | oi.read(value2Bytes, 0, value2Bytes.length);
263 | value2 = StringUtils.utf8BytesToString(value2Bytes);
264 |
265 | array1 = new int[oi.readInt()];
266 | for (int i = 0; i < array1.length; i++) {
267 | array1[i] = oi.readInt();
268 | }
269 |
270 | array2Bytes = new byte[oi.readInt()][];
271 | array2 = new String[array2Bytes.length];
272 | for (int i = 0; i < array2Bytes.length; i++) {
273 | array2Bytes[i] = new byte[oi.readInt()];
274 | oi.read(array2Bytes[i], 0, array2Bytes[i].length);
275 | array2[i] = StringUtils.utf8BytesToString(array2Bytes[i]);
276 | }
277 | }
278 |
279 | // DirectObject
280 | @Override
281 | public void serialize(DirectObjectContext doContext) {
282 | doContext.putInt(value1);
283 | doContext.putStringFast(value2);
284 | doContext.alignInt();
285 |
286 | doContext.putInt(array1.length);
287 | for (int v : array1) {
288 | doContext.putInt(v);
289 | }
290 |
291 | doContext.putInt(array2.length);
292 | for (String data : array2) {
293 | doContext.putStringFast(data);
294 | doContext.alignInt();
295 | }
296 | }
297 |
298 | @Override
299 | public void unserialize(DirectObjectContext doContext) {
300 | value1 = doContext.getInt();
301 | value2 = doContext.getStringFast();
302 | doContext.alignInt();
303 |
304 | array1 = new int[doContext.getInt()];
305 | for (int i = 0; i < array1.length; i++) {
306 | array1[i] = doContext.getInt();
307 | }
308 |
309 | array2 = new String[doContext.getInt()];
310 | for (int i = 0; i < array2.length; i++) {
311 | array2[i] = doContext.getStringFast();
312 | doContext.alignInt();
313 | }
314 | }
315 |
316 | @Override
317 | public int getSerializedSize(DirectObjectContext doContext) {
318 | int p = 0;
319 | p += 4; // value1
320 | p += doContext.getStringFastLength(value2); // value2
321 | p = doContext.alignPositionInt(p);
322 | p += 4 + 4 * array1.length; // array1
323 | p += 4; // array2.length;
324 | for (String data : array2) {
325 | p += doContext.getStringFastLength(data); // array2[i]
326 | p = doContext.alignPositionInt(p);
327 | }
328 |
329 | return p;
330 | }
331 |
332 | @Override
333 | public String toString() {
334 | return "Bean{" +
335 | "value1=" + value1 +
336 | ", value2='" + value2 + '\'' +
337 | ", value2Bytes=" + Arrays.toString(value2Bytes) +
338 | ", array1=" + Arrays.toString(array1) +
339 | ", array2=" + Arrays.toString(array2) +
340 | ", array2Bytes=" + Arrays.toString(array2Bytes) +
341 | ", hashValue=" + hashValue +
342 | '}';
343 | }
344 | }
345 |
--------------------------------------------------------------------------------