gpt4 book ai didi

java - 使用新的 java.time API 解析时区非常慢

转载 作者:IT老高 更新时间:2023-10-28 21:09:10 29 4
gpt4 key购买 nike

我刚刚将一个模块从旧的 java 日期迁移到新的 java.time API,并注意到性能大幅下降。它归结为使用时区解析日期(我一次解析数百万个日期)。

解析没有时区的日期字符串 (yyyy/MM/dd HH:mm:ss) 速度很快 - 比使用旧的 java 日期快大约 2 倍,每秒大约 150 万次操作在我的电脑上。

但是,当模式包含时区 (yyyy/MM/dd HH:mm:ss z) 时,使用新的 java.time API,而使用旧 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<iterations; i++) {
bh.consume(simpleDateFormat.parse("2000/12/12 12:12:12"));
}
}

@Benchmark
public void oldFormat_withZone(Blackhole bh, DateParsingBenchmark st) throws ParseException {

SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z");

for(int i=0; i<iterations; i++) {
bh.consume(simpleDateFormat.parse("2000/12/12 12:12:12 CET"));
}
}

@Benchmark
public void newFormat_noZone(Blackhole bh, DateParsingBenchmark st) {

DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy/MM/dd HH:mm:ss").toFormatter();

for(int i=0; i<iterations; i++) {
bh.consume(dateTimeFormatter.parse("2000/12/12 12:12:12"));
}
}

@Benchmark
public void newFormat_withZone(Blackhole bh, DateParsingBenchmark st) {

DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy/MM/dd HH:mm:ss z").toFormatter();

for(int i=0; i<iterations; i++) {
bh.consume(dateTimeFormatter.parse("2000/12/12 12:12:12 CET"));
}
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(DateParsingBenchmark.class.getSimpleName()).build();
new Runner(opt).run();
}
}

以及 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<iterations; i++) {
bh.consume(dateTimeFormatter.parse("CET"));
}
}

java.time 包中有一个名为 ZoneTextPrinterParser 的类,它在内部制作每个 parse 中所有可用时区集的副本() 调用(通过 ZoneRulesProvider.getAvailableZoneIds()),这占区域解析所花费时间的 99%。

好吧,那么答案可能是编写我自己的区域解析器,这也不太好,因为那时我无法通过 appendPattern() 构建 DateTimeFormatter >.

最佳答案

如您的问题和我的评论中所述,ZoneRulesProvider.getAvailableZoneIds()每次需要解析时区时,都会创建一组新的所有可用时区字符串表示形式(static final ConcurrentMap<String, ZoneRulesProvider> ZONES 的键)。1

幸运的是,ZoneRulesProviderabstract被设计为子类的类。方法protected abstract Set<String> provideZoneIds()负责填充ZONES .因此,如果子类提前知道要使用的所有个时区,则它只能提供所需的时区。由于该类将提供比包含数百个条目的默认提供程序更少的条目,因此它有可能显着减少 getAvailableZoneIds() 的调用时间。 .

ZoneRulesProvider API提供有关如何注册的说明。注意provider不能注销,只能补充,所以去掉默认provider并添加自己的provider并不是一件简单的事情。系统属性java.time.zone.DefaultZoneRulesProvider定义默认提供者。如果返回 null (通过 System.getProperty("..." )然后加载 JVM 臭名昭著的提供程序。使用 System.setProperty("...", "fully-qualified name of a concrete ZoneRulesProvider class")可以提供自己的提供商,这是在第 2 段中讨论的。

最后,我建议:

  1. 子类 abstract class ZoneRulesProvider
  2. 实现 protected abstract Set<String> provideZoneIds()仅包含所需的时区。
  3. 将系统属性设置为此类。

我自己没有这样做,但我确信它会由于某种原因而失败认为它会起作用。


1在问题的评论中建议调用的确切性质可能在 1.8 版本之间发生了变化。

编辑:找到更多信息

前面提到的默认ZoneRulesProviderfinal 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

如果有人找到一种方法来创建一个仅包含所需区域的类似文件并加载该文件,那么性能问题将可能不会肯定会得到解决。

关于java - 使用新的 java.time API 解析时区非常慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34374464/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com