高频交易 HFT 系统目标是降低I/O延迟,当你写磁盘或通过网络传输不可避免地你将延迟差性能导入你的系统,一种解决方案是使用NIO (non-blocking I/O). 对于磁盘I/O使用内存映射文件memory-mapped file ,而对于网络I/O 你可以使用非堵塞通道. NIO是一种异步输出,意味着你将字节传交付给OS (i.e. copy them to a direct byte buffer),希望底层OS能够做好这部分工作。
下面以日志记录为案例:
先树立的一个比较样本,如果日志信息在内存中直接拷贝,大概需要多少时间,用ByteBuffer 带来的结果:
String msg = "This is a log message not so small that you can use to test. I hope you have fun and it works well!"; byte[] msgBytes = msg.getBytes(); ByteBuffer bb = ByteBuffer.allocate(1024); long start = System.nanoTime(); bb.put(msgBytes); long end = System.nanoTime(); System.out.println("Time: " + (end - start));
|
内存中直接拷贝最短执行时间:
Avg Time: 26.42 nanos下面引入文件磁盘系统,比较 内存映射文件和异步日志 哪个最快。
1.1MappedByteBuffer内存映射文件带来的延迟
File file = new File("mMapFile.txt"); RandomAccessFile raf = new RandomAccessFile(file, "rw"); MappedByteBuffer mappedBuffer = raf.getChannel().map(MapMode.READ_WRITE, 0, 1024); long start = System.nanoTime(); mappedBuffer.put(msgBytes); long end = System.nanoTime(); System.out.println("Time: " + (end - start));
|
执行时间:
Avg Time: 53.52 nanos
2. Lock-free Queue 无锁队列也就是异步的延迟:
Builder<ByteBuffer> builder = new Builder<ByteBuffer>() { @[author]Override[/author] public ByteBuffer newInstance() { // max log message size is 1024 return ByteBuffer.allocate(1024); } }; final BatchingQueue<ByteBuffer> queue = new AtomicQueue<ByteBuffer>(1024 * 1024, builder); final WaitStrategy consumerWaitStrategy = new ParkWaitStrategy(); Thread asyncLogger = new Thread(new Runnable() { @[author]Override[/author] public void run() { while (true) { long avail; while((avail = queue.availableToPoll()) == 0) { consumerWaitStrategy.waitForOtherThread(); } consumerWaitStrategy.reset(); for(int i = 0; i < avail; i++) { ByteBuffer bb = queue.poll(); bb.flip(); if (bb.remaining() == 0) { // EOF return; } else { // log your byte buffer here // any way you want since you // are not disrupting the main // thread anymore... } bb.clear(); } queue.donePolling(); } } }, "AsyncLogger"); asyncLogger.start(); long start = System.nanoTime(); ByteBuffer bb; while((bb = queue.nextToDispatch()) == null) { // busy spin in case queue is full } bb.put(msgBytes); queue.flush(true); // true = lazySet() long end = System.nanoTime(); System.out.println("Time: " + (end - start));
|
执行时间:
Avg Time: 30.95 nanos
总结,异步日志时间要比使用内存映射文件委托底层OS操作更快。
异步日志 vs. 内存映射文件Asynchronous logging versus Memory Mapped Files | MentaBlog