├── 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 t : TEST_CLASSES) { 54 | for (Class s : STORE_CLASSES) { 55 | runTest(t, s); 56 | } 57 | } 58 | } 59 | 60 | private static void runTest(Class testClass, Class 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 testClass, Class 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 testClass, Class 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 | --------------------------------------------------------------------------------