我只是将模块从旧的Java日期迁移到新的java.time API,并注意到性能大幅下降.它归结为用时区解析日期(我一次解析数百万个).
解析没有时区(yyyy/MM/dd HH:mm:ss
)的日期字符串很快 - 比旧的Java日期快2倍,在我的电脑上每秒约1.5M操作.
但是,当模式包含时区(yyyy/MM/dd HH:mm:ss z
)时,使用新java.time
API 的性能下降约15倍,而使用旧API时,性能与没有时区的速度一样快.请参阅下面的性能基准.
有没有人知道我是否能以某种方式使用新的java.time
API 快速解析这些字符串?目前,作为一种解决方法,我使用旧的API进行解析,然后将其转换Date
为Instant,这不是特别好.
import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @OutputTimeUnit(TimeUnit.MILLISECONDS) @BenchmarkMode(Mode.AverageTime) @OperationsPerInvocation(1) @Fork(1) @Warmup(iterations = 3) @Measurement(iterations = 5) @State(Scope.Thread) public class DateParsingBenchmark { private final int iterations = 100000; @Benchmark public void oldFormat_noZone(Blackhole bh, DateParsingBenchmark st) throws ParseException { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); for(int i=0; i以及100K操作的结果:
Benchmark Mode Cnt Score Error Units DateParsingBenchmark.newFormat_noZone avgt 5 61.165 ± 11.173 ms/op DateParsingBenchmark.newFormat_withZone avgt 5 1662.370 ± 191.013 ms/op DateParsingBenchmark.oldFormat_noZone avgt 5 93.317 ± 29.307 ms/op DateParsingBenchmark.oldFormat_withZone avgt 5 107.247 ± 24.322 ms/op更新:
我刚刚对java.time类进行了一些分析,实际上,时区解析器似乎实现效率非常低.解析一个独立的时区只会导致所有的缓慢.
@Benchmark public void newFormat_zoneOnly(Blackhole bh, DateParsingBenchmark st) { DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder() .appendPattern("z").toFormatter(); for(int i=0; i
ZoneTextPrinterParser
在java.time
bundle中调用了一个类,它在内部制作每个parse()
调用(viaZoneRulesProvider.getAvailableZoneIds()
)中所有可用时区的集合的副本,这对于区域解析花费的99%的时间负责.那么,答案可能是编写我自己的区域解析器,这也不会太好,因为那时我无法构建
DateTimeFormatter
通道appendPattern()
.
1> user1803551..:如您的问题和我的评论中所述,每次需要解析时区时,
ZoneRulesProvider.getAvailableZoneIds()
都会创建一组新的所有可用时区的字符串表示形式(其中的键static final ConcurrentMap
).1ZONES 幸运的是,a
ZoneRulesProvider
是一个abstract
设计为子类的类.该方法protected abstract Set
负责填充provideZoneIds() ZONES
.因此,如果子类提前知道要使用的所有时区,则子类只能提供所需的时区.由于该类提供的条目少于默认提供程序(包含数百个条目),因此它有可能显着减少调用时间getAvailableZoneIds()
.该ZoneRulesProvider API提供了如何注册一个指令.请注意,提供程序无法取消注册,只能进行补充,因此删除默认提供程序并添加自己的提供程序并不是一件简单的事情.system属性
java.time.zone.DefaultZoneRulesProvider
定义默认提供程序.如果它返回null
(viaSystem.getProperty("..."
)则加载JVM的臭名昭着的提供者.使用System.setProperty("...", "fully-qualified name of a concrete ZoneRulesProvider class")
一个可以提供他们自己的提供者,这是第2段中讨论的提供者.最后,我建议:
子类
abstract class ZoneRulesProvider
实现了
protected abstract Set
只用所需的时区.provideZoneIds() 将系统属性设置为此类.
我自己没有这样做,但我
确信它会因为某些原因而失败,认为它会起作用.
1在问题的评论中建议,调用的确切性质可能在1.8版本之间发生了变化.
编辑:找到更多信息
上述默认
ZoneRulesProvider
值final class TzdbZoneRulesProvider
位于java.time.zone
.从路径中读取该类中的区域:(JAVA_HOME/lib/tzdb.dat
在我的例子中,它位于JDK的JRE中).该文件确实包含许多区域,这里是一个片段:TZDB 2014cJ Africa/Abidjan Africa/Accra Africa/Addis_Ababa Africa/Algiers Africa/Asmara Africa/Asmera Africa/Bamako Africa/Bangui Africa/Banjul Africa/Bissau Africa/Blantyre Africa/Brazzaville Africa/Bujumbura Africa/Cairo Africa/Casablanca Africa/Ceuta Africa/Conakry Africa/Dakar Africa/Dar_es_Salaam Africa/Djibouti Africa/Douala Africa/El_Aaiun Africa/Freetown Africa/Gaborone Africa/Harare Africa/Johannesburg Africa/Juba Africa/Kampala Africa/Khartoum Africa/Kigali Africa/Kinshasa Africa/Lagos Africa/Libreville Africa/Lome Africa/Luanda Africa/Lubumbashi Africa/Lusaka Africa/Malabo Africa/Maputo Africa/Maseru Africa/Mbabane Africa/Mogadishu Africa/Monrovia Africa/Nairobi Africa/Ndjamena Africa/Niamey Africa/Nouakchott Africa/Ouagadougou Africa/Porto-Novo Africa/Sao_Tome Africa/Timbuktu Africa/Tripoli Africa/Tunis Africa/Windhoek America/Adak America/Anchorage America/Anguilla America/Antigua America/Araguaina America/Argentina/Buenos_Aires America/Argentina/Catamarca America/Argentina/ComodRivadavia America/Argentina/Cordoba America/Argentina/Jujuy America/Argentina/La_Rioja America/Argentina/Mendoza America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Aruba America/Asuncion America/Atikokan America/Atka America/Bahia然后,如果找到一种方法来创建仅包含所需区域的类似文件并加载该区域,则
可能无法确定性能问题.