这个问题特别适用于快速序列化库。 https://github.com/RuedigerMoeller/fast-serialization
我在Windows 7上使用FSTLongOffheapMap(版本2.29),jdk 1.7来存储一些对象。我测试了存储物体的延迟,99.99百分位数达到约100微米。这是优秀,考虑到我在Windows上运行它+没有编写自定义序列化器+没有太多调整GC。
但是,如果按照以下方式更改设置,性能会急剧下降:
a)创建一个生产者线程和一个由ArrayBlockingQueue管道的消费者线程。
b)让生产者生产50,000个对象,并尽快将它们填入队列。
c)使用者线程拉出对象,将其分派给监听器,然后将其存储在堆外映射中。
在此设置中,第99.99百分位延迟达到 ~135毫秒! 但最大的惊喜是,如果我只是评论执行放置的线路,延迟下降到~400微秒。这对我来说似乎不合逻辑,因为我从之前的测试中得知,地图中放置的延迟仅为100微秒。
以下是重现我的发现的测试,我将不胜感激任何建议/提示/想法。运行测试所需的唯一外部库是Gil Tene的HdrHistogram。 https://github.com/HdrHistogram/HdrHistogram
- 非常感谢
package com.mine.serialization.perf;
import java.io.*;
import java.util.*;
import org.nustaq.offheap.*;
import org.nustaq.serialization.simpleapi.*;
public final class MyFSTSerializer{
private final boolean toStore;
private final String fileName;
private final long memorySize;
private final FSTCoder fastCoder;
private final FSTLongOffheapMap<MktDataEvent> offHeapMap;
public MyFSTSerializer( boolean toStore, String location, String journalName, FSTCoder fastCoder, long memorySize, int count ) throws Exception{
this.toStore = toStore;
this.fileName = location + File.separator + journalName + ".mmf";
this.memorySize = memorySize;
this.fastCoder = fastCoder;
this.offHeapMap = new FSTLongOffheapMap<>( fileName, memorySize, count, fastCoder );
}
public final boolean toStore( ){
return toStore;
}
public final String getFilename( ){
return fileName;
}
public final void start( ){
fastCoder.getConf().setCrossPlatform( false );
fastCoder.getConf().setPreferSpeed( true );
fastCoder.getConf().setShareReferences( false );
fastCoder.getConf().registerClass( Long.class, MktDataEvent.class );
System.out.println("Journaling started at " + fileName + " with Memory " + memorySize ) ;
}
public final void storeEvent( MktDataEvent event ){
offHeapMap.put( event.getSequenceId(), event );
}
public final Collection<MktDataEvent> retrieveAllEvents( ){
Map<Long, MktDataEvent> retrievedMap = new LinkedHashMap<>();
for( Iterator<MktDataEvent> iterator = offHeapMap.values(); iterator.hasNext(); ){
MktDataEvent event = (MktDataEvent) iterator.next();
retrievedMap.put( event.getSequenceId(), event );
}
return retrievedMap.values();
}
public final void stop( ){
try{
offHeapMap.free( );
System.out.println("Stopped Journal and freed memory." );
}catch( Exception e ){
e.printStackTrace( );
}
}
}
package com.mine.serialization.perf;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
public final class MktDataEvent implements Serializable{
private final long sequenceId;
private final long creationTime;
private final String symbol;
private final double bidPrice;
private final long bidQuantity;
private final double askPrice;
private final long askQuantity;
private final static long serialVersionUID = 1L;
private final static AtomicLong SEQUENCE = new AtomicLong();
public MktDataEvent( String symbol, double bidPrice, long bidQuantity, double askPrice, long askQuantity ){
this.creationTime = System.nanoTime( );
this.sequenceId = SEQUENCE.incrementAndGet();
this.symbol = symbol;
this.bidPrice = bidPrice;
this.bidQuantity = bidQuantity;
this.askPrice = askPrice;
this.askQuantity = askQuantity;
}
public final long getSequenceId( ){
return sequenceId;
}
public final long getCreationTime( ){
return creationTime;
}
public final String getSymbol(){
return symbol;
}
public final double getBidPrice( ){
return bidPrice;
}
public final long getBidQuantity( ){
return bidQuantity;
}
public final double getAskPrice( ){
return askPrice;
}
public final long getAskQuantity( ){
return askQuantity;
}
}
// --------------------------------------------- -------------------
package com.mine.serialization.perf;
import java.util.*;
import java.util.concurrent.*;
public final class MktDataDispatcher implements Runnable{
private volatile boolean keepDispatching;
private final ExecutorService service;
private final MyFSTSerializer serializer;
private final MktDataListener listener;
private final AbstractQueue<MktDataEvent> eventQueue;
public MktDataDispatcher( int queueSize, MyFSTSerializer serializer, MktDataListener listener ){
this.serializer = serializer;
this.listener = listener;
this.eventQueue = new ArrayBlockingQueue<MktDataEvent>( queueSize );
this.service = Executors.newFixedThreadPool( 1 );
}
public final void start( ){
serializer.start( );
keepDispatching = true;
service.execute( this );
}
public final boolean enqueue( final MktDataEvent event ){
return eventQueue.offer( event );
}
@Override
public final void run( ){
while( keepDispatching ){
try{
MktDataEvent event = eventQueue.poll();
if( event == null ){
Thread.yield();
continue;
}
if( serializer.toStore() ){
serializer.storeEvent( event );
}
listener.update( event );
}catch( Exception e ){
e.printStackTrace( );
}
}
}
protected final int getQueueSize( ){
return eventQueue.size( );
}
public final void stop(){
serializer.stop( );
keepDispatching = false;
service.shutdown();
}
public interface MktDataListener{
public boolean update( MktDataEvent event );
}
}
package com.mine.serialization.perf;
import java.io.*;
import java.util.concurrent.*;
import org.HdrHistogram.*;
import org.nustaq.serialization.simpleapi.*;
import com.mine.serialization.perf.MktDataDispatcher.*;
public final class TestFSTSerializer{
protected static void printResult( Histogram histogram ){
System.out.println( "\nDetail Result (in micros)");
System.out.println( "------------------------------------------------------------------");
histogram.outputPercentileDistribution( System.out, 1000.0 );
double valueAt99Percentile = histogram.getValueAtPercentile( 99.99d );
System.out.println( "\nValue 99.99th percentile >> " + valueAt99Percentile/1000.0 );
}
protected static MyFSTSerializer createFSTSerializer( boolean toStore, int eventCount, int memorySizeOf1Object ) throws Exception{
long expectedMemory = memorySizeOf1Object * eventCount;
String fileLocation = "C:\\Temp";
String journalName = "Test";
MyFSTSerializer ser = new MyFSTSerializer( toStore, fileLocation, journalName, new DefaultCoder(), expectedMemory, eventCount );
return ser;
}
protected static void destroyFSTSerializer( MyFSTSerializer serializer ){
if( serializer != null ){
serializer.stop();
boolean deleted = new File( serializer.getFilename() ).delete();
if( deleted ){
System.out.println( "Deleted file from " + serializer.getFilename());
}else{
throw new RuntimeException( "TEST FAILED as we failed to delete file " + serializer.getFilename() );
}
}
}
public static void testOffHeapPersistence( ){
MyFSTSerializer serializer= null;
try{
int eventCount = 50000;
int memorySizeOf1Object = 1000;
Histogram histogram = new Histogram( TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS), 2);
System.out.println( "Testing off heap persistence performance of FSTLongOffheapMap by storing " + eventCount + " events.");
serializer = createFSTSerializer( true, eventCount, memorySizeOf1Object );
serializer.start( );
for( int i =0; i<eventCount; i++ ){
MktDataEvent event = new MktDataEvent( "EDM6", 99.0, (100 + i), 99.50, (200 + i) );
serializer.storeEvent( event );
histogram.recordValue(System.nanoTime() - event.getCreationTime() );
}
int retrievedEventSize = serializer.retrieveAllEvents().size();
if( eventCount != retrievedEventSize )
throw new RuntimeException("Store failed as we stored " + eventCount + " events but retrieved " + retrievedEventSize );
printResult( histogram );
}catch( Exception e ){
throw new RuntimeException("TEST FAILED as ", e);
}finally{
destroyFSTSerializer( serializer );
}
}
public static void testDispatchAndPersistence( boolean toStore ) throws Exception{
int eventCount = 50000;
int memorySizeOf1Object = 1000;
DummyListener listener = new DummyListener( );
MyFSTSerializer serializer = createFSTSerializer( toStore, eventCount, memorySizeOf1Object );
MktDataDispatcher dispatcher = new MktDataDispatcher( eventCount, serializer, listener );
if( toStore ){
System.out.println( "Testing off heap persistence with dispathcer performance of FSTLongOffheapMap by storing " + eventCount + " events.");
}else{
System.out.println( "Testing off heap persistence with dispathcer performance of FSTLongOffheapMap WITHOUT storing " + eventCount + " events.");
}
dispatcher.start();
System.gc();
Thread.sleep( 3000 );
for( int i = 0; i< eventCount; i++ ){
MktDataEvent event = new MktDataEvent( "EDM6", 99.0, (100 + i), 99.50, (200 + i) );
dispatcher.enqueue( event );
}
//Let the listener get all the elements
while( (dispatcher.getQueueSize() != 0) ){
Thread.yield();
}
Thread.sleep( 2000 );
dispatcher.stop();
listener.generateLatencyStats();
destroyFSTSerializer( serializer );
}
public static class DummyListener implements MktDataListener{
private final Histogram histogram;
public DummyListener( ){
this.histogram = new Histogram( TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS), 2);
}
@Override
public final boolean update( MktDataEvent event ){
histogram.recordValue( (System.nanoTime() - event.getCreationTime()) );
return true;
}
public final void generateLatencyStats( ){
histogram.outputPercentileDistribution( System.out, 1000.0 );
double valueAt99Percentile = histogram.getValueAtPercentile( 99.99d );
System.out.println( "\nValue at 99.99th percentile (micros) >> " + valueAt99Percentile/1000.0 );
}
}
public static void main( String ... args ) throws Exception{
testOffHeapPersistence( );
System.gc();
Thread.sleep( 2000 );
testDispatchAndPersistence( false );
System.gc();
Thread.sleep( 2000 );
testDispatchAndPersistence( true );
}
}
答案 0 :(得分:1)
您好(我是FST的作者):
我认为测试存在缺陷:
当在循环中运行同步(没有队列+线程上下文切换)测试时(=正确的预热)我得到 0.7 micros 的平均值和 14个微处理器的最大异常值(在地图中加倍数量的元素)存储单个事件 这是FST的性能,您看到的丢失和延迟是由排队/线程上下文切换引起的。此外,测试有一个缺陷:
在事件创建过程中,需要花费一些时间将一连串50k事件放入队列中。由于put事件比存储事件要快得多,你会得到积累:N'事件得到所有0..n-1事件积累的延迟;)。
由于缺少JVM预热,第一次运行似乎很好:事件创建速度慢,因此事件不会排队。
其他问题:
1)主要:没有WARMUP。在查看数字之前,先进行循环并让测试运行几次(如10)。
2)(次要)通过优惠进行入场而不检查结果
3)如果没有事件可用,则轮询队列进行收益,这可能导致不确定的延迟峰值。只有在退避后才能收益。
你应该在每个〜1-2微秒的时间内将一个事件放入队列中,以避免排队事件并以这种方式测量聚合时间。
将TestFSTSerializer更改为:
for( int i = 0; i< eventCount; i++ ){
MktDataEvent event = new MktDataEvent( "EDM6", 99.0, (100 + i), 99.50, (200 + i) );
dispatcher.enqueue( event );
long nanos = System.nanoTime();
while( System.nanoTime() - nanos < 3000 )
Thread.yield();
}
和main方法(预热,忽略第一次运行):
public static void main( String ... args ) throws Exception{
for (int i = 0; i < 1000; i++) {
System.gc();
Thread.sleep( 2000 );
System.out.println("start test ==>");
testDispatchAndPersistence( true );
//testOffHeapPersistence();
}
的产率:
[平均值= 5.19,StdDeviation = 29.67]
[最大= 544.77,总计数= 50000]
[Buckets = 23,SubBuckets = 256]
请注意,线程上下文切换花费您3-8微秒(因此高端内核绕过网络的速度几乎与线程之间的排队速度相当!!)。
您可以尝试使用比java.concurrent更快的队列来进一步减少延迟。
注意以后的测试:由于持久性依赖于操作系统的回写急切,您需要调整操作系统设置以非常急切地回写和/或使用SSD。
请参阅https://github.com/RuedigerMoeller/fast-serialization/tree/master/src/test/ser/offheaplatency了解修改后的来源
答案 1 :(得分:0)
我用来提高地图性能的一个技巧是为它们提供初始容量。
我的意思是你的LinkedHashMap应该初始化,其容量大约是事件数量级
你希望在检索之前推进。