Java读取Level-1行情dbf文件极致优化(3)

时间:2023-01-22 12:23:17

最近架构一个项目,实现行情的接入和分发,需要达到极致的低时延特性,这对于证券系统是非常重要的。接入的行情源是可以配置,既可以是Level-1,也可以是Level-2或其他第三方的源。虽然Level-1行情没有Level-2快,但是作为系统支持的行情源,我们还是需要优化它,使得从文件读取,到用户通过socket收到行情,端到端的时延尽可能的低。本文主要介绍对level-1行情dbf文件读取的极致优化方案。相信对其他的dbf文件读取应该也有借鉴意义。

Level-1行情是由行情小站,定时每隔几秒把dbf文件(上海是show2003.dbf,深圳是sjshq.dbf)更新一遍,用新的行情替换掉旧的。我们的目标就是,在新文件完成更新后,在最短时间内将文件读取到内存,把每一行转化为对象,把每个列转化为对应的数据类型。

我们一共采用了6种优化方式。

我们在上文《Java读取Level-1行情dbf文件极致优化(1)》Java读取Level-1行情dbf文件极致优化(2)》中,已经介绍了4种优化策略:

优化一:采用内存硬盘(RamDisk)
优化二:采用JNotify,用通知替代轮询

优化三:采用NIO读取文件
优化四:减少读取文件时内存反复分配和GC

优化五:字段读取优化

行情dbf文件很多字段是价格类型的字段,带2位或者3位小数,从dbf读取他们的后,我们会把它们保存在Long类型或者Int类型,而不是Float或Double类型,比如1.23,转换为1230保存。因为Float型或Double型会丢失精度。

如果不优化,读取步骤为:

1,从byte[]对应的偏移中读取并保存到String中。

2,对String做trim操作

3,把String转换为Float类型

4,把Float类型乘以1000并强转为Long类型。

不用多说,以上的过程一定是低效的,光前两步就涉及到2次字符串拷贝,2次对象创建。第三步效率也不高。我这里通过优化,在DBFReader.java中添加一个get_long_efficiently_and_multiply_1000方法,将4个步骤合并为一步,通过一次扫描得到结果。

public long get_long_efficiently_and_multiply_1000(byte[] src, final int index)
{
long multiplicand = 3;
long result =0;
Field field = getFields()[index];
boolean in_decimal_part = false;
boolean negative = false;
int offset = field.getOffset();
int length = field.getLength();
int end = offset+length;
for(int i =field.getOffset(); i< end; i++)
{
byte ch = src[i]; if(ch>=48 && ch<=57) //如果是数字
{
result *= 10;
result += ch-48;
if(in_decimal_part)
multiplicand--;
if(multiplicand==0) break;
continue;
} if(ch==32) //如果是空格
continue; if(ch == 46) //如果是小数点
{
in_decimal_part = true;
continue;
} if(ch == '-') //如果是负号
{
negative = true;
} throw new NumberFormatException(); } if(multiplicand == 3)
result *= 1000;
else if (multiplicand == 2)
result *=100;
else if (multiplicand == 1)
result *=10; if(negative)
{
result= 0 - result;
} return result;
}

上面的算法负责读取字段转换为数字的同时,对它乘以1000。并且代码中尽量优化了执行步骤。

对于整形的读取,我们也进行了优化,添加一个get_long_efficiently:

public long get_long_efficiently(byte[] src, final int index)
{
long result =0;
boolean negative = false;
Field field = getFields()[index];
for(int i =field.getOffset(); i< field.getOffset()+ field.getLength(); i++)
{
byte ch = src[i];
if(ch>=48 && ch<=57) //如果是数字
{
result = result*10 + (src[i]-48);
continue;
} if(src[i]==32) //如果是空格
continue; if(ch == '-') //如果是负号
{
negative = true;
} throw new NumberFormatException();
} if(negative)
{
result= 0 - result;
} return result;
}

以上的2个算法并不复杂,但却非常关键,一个dbf文件包含大约5000行,每行包括20~30个Float类型或者Int类型的字段,该优化涉及10万+个字段的读取。测试下来,这步改进将读取速度从50ms-70ms提升至15ms至20ms,细节在魔鬼当中,这是速度提升最快的一项优化。

(优化五的代码在改进的DBFReader中,上午中已经提供下载,这里再提供下载链接:Java读取Level-1行情dbf文件极致优化(3)DBFReader库 )

优化六:线程池并行处理

对5000多个行进行字段读取并转换成对象,采用多线程处理是最自然不过的优化方式。

一般我们采用的方法是把任务分成等份的块,每个线程处理一大块。比如,如果采用5个线程处理,那么把5000行分成1000个行一块,每个线程处理一块。这样看貌似公平,其实不然,因为我们的操作系统是分时操作系统,每个线程开始工作的时间,占用的CPU时间片,和任务的强度都不完全一致。等分的办法貌似平均,但是很有可能导致有些线程完成工作了,另外一些还有很多没做完。

这里介绍一种我喜欢的任务分配方式:每个线程每次从5000个行的任务中申请一小块,比如16个行,完成后,再申请16个行。这样快的线程就会多工作些,慢的就少工作些,直到所有的行处理完毕。那么,这些线程怎么协调呢,任务分配岂不是要用到锁?不用锁,我们采用CAS机制就能做到(实际用的是AtomicInteger,AtomicInteger就是基于CAS实现的),这里不解释太多了。看代码:

class ReaderTask implements Runnable {
Collector collector;
List<byte[]> recordList;
CountDownLatch countDownLatch;
AtomicInteger cursor;
DBFReader reader; public ReaderTask(Collector collector, DBFReader dbfreader, List<byte[]> recordList, AtomicInteger cursor,
CountDownLatch countDownLatch) {
this.collector = collector;
this.reader = dbfreader;
this.recordList = recordList;
this.cursor = cursor;
this.countDownLatch = countDownLatch;
} @Override
public void run() {
try {
int length = recordList.size();
do {
final int step = 16; //每次分配16行给该线程处理。
int endIndex = cursor.addAndGet(step);
int startIndex = endIndex - step ; for (int i = startIndex; i < endIndex && i < length; i++) {
byte[] row = recordList.get(i);
MarketRealtimeData SHData = new MarketRealtimeData();
SHData.setMarketType(Constants.MARKET_SH_STOCK);
SHData.setIdNum(reader.get_string_efficiently(row, 0));
SHData.setPrefix(reader.get_string_efficiently(row, 1));
SHData.setPreClosePrice(reader.get_long_efficiently_and_multiply_1000(row, 2));
SHData.setOpenPrice(reader.get_long_efficiently_and_multiply_1000(row, 3));
SHData.setTurnover(reader.get_long_efficiently_and_multiply_1000(row, 4));
SHData.setHighPrice(reader.get_long_efficiently_and_multiply_1000(row, 5));
SHData.setLowPrice(reader.get_long_efficiently_and_multiply_1000(row, 6));
SHData.setMatchPrice(reader.get_long_efficiently_and_multiply_1000(row, 7));
//读取所有的Field,以下省略若干行
//... ...
//... ... if (collector != null) {
collector.collect(SHData);
}
}
} while (cursor.get() < length);
} finally {
if (countDownLatch != null)
countDownLatch.countDown();
}
}
}
private void readHangqingFile(String path, String name) throws Exception {
// Long t1 = System.nanoTime();
DBFReader dbfreader_SH = null;
try {
dbfreader_SH = new DBFReader(new File(path+File.separator + name));
List<byte[]> list_sh = dbfreader_SH.recordsWithOutDel_efficiently(cacheManager); AtomicInteger cursor = new AtomicInteger(0); //原子变量,用于线程间分配任务
CountDownLatch countDownLatch = new CountDownLatch(WORK_THREAD_COUNT); for (int i = 0; i < WORK_THREAD_COUNT - 1; i++) { //把任务分配给线程池多个线程
ReaderTask task = new ReaderTask(collector, dbfreader_SH, list_sh, cursor, countDownLatch);
globalExecutor.execute(task);
}
new ReaderTask(collector, dbfreader_SH, list_sh, cursor, countDownLatch).run(); //当前线程自己也作为工作线程
countDownLatch.await();
//Long t2 = System.nanoTime();
//System.out.println("speed time on read and object:" + (t2 - t1)); } finally {
if (dbfreader_SH != null)
dbfreader_SH.close();
}
}

测试表明,在使用4个线程并行处理的情况下,处理时间从15ms-20ms缩短至4ms-7ms。

在使用本文章介绍的所有优化方法,整个读取效率从耗时300ms以上,优化至5ms-10ms之间。我们讨论的是从文件更新始,到完成文件读取,完成5000多个对象,100,000个字段的转换的总耗时。

如果继续深入,我们可能还有不少细节可以改进。测试表明,时延的稳定性还不够好,很可能是由于GC造成的,我们还可以从减少对象的创建,以减少性能损耗,减少GC;并且控制GC执行的时间,让GC在空闲时执行等方面优化。

Binhua Liu原创文章,转载请注明原地址http://www.cnblogs.com/Binhua-Liu/p/5616761.html

Java读取Level-1行情dbf文件极致优化(3)的更多相关文章

  1. Java读取Level-1行情dbf文件极致优化(2)

    最近架构一个项目,实现行情的接入和分发,需要达到极致的低时延特性,这对于证券系统是非常重要的.接入的行情源是可以配置,既可以是Level-1,也可以是Level-2或其他第三方的源.虽然Level-1 ...

  2. Java读取Level-1行情dbf文件极致优化(1)

    最近架构一个项目,实现行情的接入和分发,需要达到极致的低时延特性,这对于证券系统是非常重要的.接入的行情源是可以配置,既可以是Level-1,也可以是Level-2或其他第三方的源.虽然Level-1 ...

  3. 【转】Java读取matlab的&period;mat数据文件

    参考:Java读取mat文件 下载链接:ujmp  jmatio 下载完两个.jar文件之后,如何引用到java项目当中?项目名称->右键->Property->Java Build ...

  4. 解决:java 读取 resources 下面的 json 文件

    前言:java 读取 工程下的配置文件,文件类型为 json(*.json),记录一下始终读取不到 json 文件的坑.maven项目 直接上工具类代码 package com.yule.compon ...

  5. java读取resource&sol;通过文件名获取文件类型

    java读取resource java读取resource目录下文件的方法: 借助Guava库的Resource类 Resources.getResource("test.txt" ...

  6. java读取目录下所有csv文件数据,存入三维数组并返回

    package dwzx.com.get; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; ...

  7. java 读取固定目录下的文件(和上篇差点儿相同)

    package gao.org; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Fi ...

  8. java读取jar包中的文件

    随手写了一个java小工具,maven打包成功后,发现工具总是读不到打在jar包中的文件信息,要读取的文件位于 /src/main/resources 目录下,打包成功后,文件就在jar包中根目录下, ...

  9. java读取package中的properties文件java&period;util&period;MissingResourceException

    文件结构: /build/classes/d914/Hello.class /build/classes/d914/mess.properties /build/classes/d914/mess_z ...

随机推荐

  1. linux几个常用的命令及获取帮助的方法

    date:时间管理,可以显示.修改系统时间.  设定时间:格式:MMDDhhmm[[cc]yy][.ss]其中MM为月分,DD为日期,hh为小时,mm为分钟,CC为年的前两位 YY为年分的后两位,.s ...

  2. sys&period;path和os&period;path

    sys.path和os.path1.sys.path是python搜索模块的路径集合,是个list:os.path是os的一个模块,是操作文件和目录的模块 2.sys.path和PYTHONPATH首 ...

  3. CSS3卷角

    众所周知,border-radius 属性可以用来设置圆角,但很少人知道它还可以做很多不规则的犄角.卷角(rounded corners) 工作原理: 一.独立属性:border-bottom-lef ...

  4. Android开发需要注意的坑

    Android开发需要注意的坑一览​对于一些Android开发过程中坑爹.细小,但又重要的错误的总结​Android开发在路上:少去踩坑,多走捷径其他参考: ​google官方版本发布图 ​umeng ...

  5. ARM处理器全解析:A8&sol;A9&sol;A15都是什么?

    前不久ARM正式宣布推出新款ARMv8架构的Cortex-A50处理器系列产品,以此来扩大ARM在高性能与低功耗领域的领先地位,进一步抢占移动终端市场份额.Cortex-A50是继Cortex-A15 ...

  6. Maven搭建SpringMVC&plus;MyBatis&plus;Json项目(多模块项目)

    一.开发环境 Eclipse:eclipse-jee-luna-SR1a-win32; JDK:jdk-8u121-windows-i586.exe; MySql:MySQL Server 5.5; ...

  7. BZOJ 1370&colon; &lbrack;Baltic2003&rsqb;Gang团伙 &lbrack;并查集 拆点 &vert; 种类并查集WA&rsqb;

    题意: 朋友的朋友是朋友,敌人的敌人是朋友:朋友形成团伙,求最多有多少团伙 种类并查集WA了一节课,原因是,只有那两种关系才成立,诸如朋友的敌人是朋友之类的都不成立! 所以拆点做吧 #include ...

  8. docker中怎样设置开机启动--随容器的启动而启动服务?

    docker可以说给我们的部署带来极大的方便和可逢凶化吉性!(懂的同学自然懂) 在初步了解之后,我们就能简单使用docker了. 刚开始玩docker时,可以基于系统级别的镜像做定制,比如基于  ce ...

  9. 转 - Linux安装python3&period;6

    https://www.cnblogs.com/kimyeee/p/7250560.html

  10. ReentrantReadWriteLock

    ReentrantReadWriteLock 这个对象,有两个内部类,readLock和writeLock,都有一个aqs的属性sync,实例化的时候,获取的是从ReentrantReadWriteL ...