Improve DateFormatter parsing performance (#8821)
Motivation: Just was looking through code and found 1 interesting place DateFormatter.tryParseMonth that was not very effective, so I decided to optimize it a bit. Modification: Changed DateFormatter.tryParseMonth method. Instead of invocation regionMatch() for every month - compare chars one by one. Result: DateFormatter.parseHttpDate method performance improved from ~3% to ~15%. Benchmark (DATE_STRING) Mode Cnt Score Error Units DateFormatter2Benchmark.parseHttpHeaderDateFormatter Sun, 27 Jan 2016 19:18:46 GMT thrpt 6 4142781.221 ± 82155.002 ops/s DateFormatter2Benchmark.parseHttpHeaderDateFormatter Sun, 27 Dec 2016 19:18:46 GMT thrpt 6 3781810.558 ± 38679.061 ops/s DateFormatter2Benchmark.parseHttpHeaderDateFormatterNew Sun, 27 Jan 2016 19:18:46 GMT thrpt 6 4372569.705 ± 30257.537 ops/s DateFormatter2Benchmark.parseHttpHeaderDateFormatterNew Sun, 27 Dec 2016 19:18:46 GMT thrpt 6 4339785.100 ± 57542.660 ops/s
This commit is contained in:
parent
cfa60552fb
commit
2e433889b2
@ -261,10 +261,6 @@ public final class DateFormatter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchMonth(String month, CharSequence txt, int tokenStart) {
|
|
||||||
return AsciiString.regionMatchesAscii(month, true, 0, txt, tokenStart, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryParseMonth(CharSequence txt, int tokenStart, int tokenEnd) {
|
private boolean tryParseMonth(CharSequence txt, int tokenStart, int tokenEnd) {
|
||||||
int len = tokenEnd - tokenStart;
|
int len = tokenEnd - tokenStart;
|
||||||
|
|
||||||
@ -272,29 +268,33 @@ public final class DateFormatter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchMonth("Jan", txt, tokenStart)) {
|
char monthChar1 = AsciiString.toLowerCase(txt.charAt(tokenStart));
|
||||||
|
char monthChar2 = AsciiString.toLowerCase(txt.charAt(tokenStart + 1));
|
||||||
|
char monthChar3 = AsciiString.toLowerCase(txt.charAt(tokenStart + 2));
|
||||||
|
|
||||||
|
if (monthChar1 == 'j' && monthChar2 == 'a' && monthChar3 == 'n') {
|
||||||
month = Calendar.JANUARY;
|
month = Calendar.JANUARY;
|
||||||
} else if (matchMonth("Feb", txt, tokenStart)) {
|
} else if (monthChar1 == 'f' && monthChar2 == 'e' && monthChar3 == 'b') {
|
||||||
month = Calendar.FEBRUARY;
|
month = Calendar.FEBRUARY;
|
||||||
} else if (matchMonth("Mar", txt, tokenStart)) {
|
} else if (monthChar1 == 'm' && monthChar2 == 'a' && monthChar3 == 'r') {
|
||||||
month = Calendar.MARCH;
|
month = Calendar.MARCH;
|
||||||
} else if (matchMonth("Apr", txt, tokenStart)) {
|
} else if (monthChar1 == 'a' && monthChar2 == 'p' && monthChar3 == 'r') {
|
||||||
month = Calendar.APRIL;
|
month = Calendar.APRIL;
|
||||||
} else if (matchMonth("May", txt, tokenStart)) {
|
} else if (monthChar1 == 'm' && monthChar2 == 'a' && monthChar3 == 'y') {
|
||||||
month = Calendar.MAY;
|
month = Calendar.MAY;
|
||||||
} else if (matchMonth("Jun", txt, tokenStart)) {
|
} else if (monthChar1 == 'j' && monthChar2 == 'u' && monthChar3 == 'n') {
|
||||||
month = Calendar.JUNE;
|
month = Calendar.JUNE;
|
||||||
} else if (matchMonth("Jul", txt, tokenStart)) {
|
} else if (monthChar1 == 'j' && monthChar2 == 'u' && monthChar3 == 'l') {
|
||||||
month = Calendar.JULY;
|
month = Calendar.JULY;
|
||||||
} else if (matchMonth("Aug", txt, tokenStart)) {
|
} else if (monthChar1 == 'a' && monthChar2 == 'u' && monthChar3 == 'g') {
|
||||||
month = Calendar.AUGUST;
|
month = Calendar.AUGUST;
|
||||||
} else if (matchMonth("Sep", txt, tokenStart)) {
|
} else if (monthChar1 == 's' && monthChar2 == 'e' && monthChar3 == 'p') {
|
||||||
month = Calendar.SEPTEMBER;
|
month = Calendar.SEPTEMBER;
|
||||||
} else if (matchMonth("Oct", txt, tokenStart)) {
|
} else if (monthChar1 == 'o' && monthChar2 == 'c' && monthChar3 == 't') {
|
||||||
month = Calendar.OCTOBER;
|
month = Calendar.OCTOBER;
|
||||||
} else if (matchMonth("Nov", txt, tokenStart)) {
|
} else if (monthChar1 == 'n' && monthChar2 == 'o' && monthChar3 == 'v') {
|
||||||
month = Calendar.NOVEMBER;
|
month = Calendar.NOVEMBER;
|
||||||
} else if (matchMonth("Dec", txt, tokenStart)) {
|
} else if (monthChar1 == 'd' && monthChar2 == 'e' && monthChar3 == 'c') {
|
||||||
month = Calendar.DECEMBER;
|
month = Calendar.DECEMBER;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
@ -111,4 +112,26 @@ public class DateFormatterTest {
|
|||||||
public void testFormat() {
|
public void testFormat() {
|
||||||
assertEquals("Sun, 6 Nov 1994 08:49:37 GMT", format(DATE));
|
assertEquals("Sun, 6 Nov 1994 08:49:37 GMT", format(DATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseAllMonths() {
|
||||||
|
assertEquals(Calendar.JANUARY, getMonth(parseHttpDate("Sun, 6 Jan 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.FEBRUARY, getMonth(parseHttpDate("Sun, 6 Feb 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.MARCH, getMonth(parseHttpDate("Sun, 6 Mar 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.APRIL, getMonth(parseHttpDate("Sun, 6 Apr 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.MAY, getMonth(parseHttpDate("Sun, 6 May 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.JUNE, getMonth(parseHttpDate("Sun, 6 Jun 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.JULY, getMonth(parseHttpDate("Sun, 6 Jul 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.AUGUST, getMonth(parseHttpDate("Sun, 6 Aug 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.SEPTEMBER, getMonth(parseHttpDate("Sun, 6 Sep 1994 08:49:37 GMT")));
|
||||||
|
assertEquals(Calendar.OCTOBER, getMonth(parseHttpDate("Sun Oct 6 08:49:37 1994")));
|
||||||
|
assertEquals(Calendar.NOVEMBER, getMonth(parseHttpDate("Sun Nov 6 08:49:37 1994")));
|
||||||
|
assertEquals(Calendar.DECEMBER, getMonth(parseHttpDate("Sun Dec 6 08:49:37 1994")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getMonth(Date referenceDate) {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.setTime(referenceDate);
|
||||||
|
return cal.get(Calendar.MONTH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1827,7 +1827,13 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
|
|||||||
return isUpperCase(b) ? (byte) (b + 32) : b;
|
return isUpperCase(b) ? (byte) (b + 32) : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char toLowerCase(char c) {
|
/**
|
||||||
|
* If the character is uppercase - converts the character to lowercase,
|
||||||
|
* otherwise returns the character as it is. Only for ASCII characters.
|
||||||
|
*
|
||||||
|
* @return lowercase ASCII character equivalent
|
||||||
|
*/
|
||||||
|
public static char toLowerCase(char c) {
|
||||||
return isUpperCase(c) ? (char) (c + 32) : c;
|
return isUpperCase(c) ? (char) (c + 32) : c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
|
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Threads;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Threads(1)
|
||||||
|
@Warmup(iterations = 5)
|
||||||
|
@Measurement(iterations = 5)
|
||||||
|
public class DateFormatter2Benchmark extends AbstractMicrobenchmark {
|
||||||
|
|
||||||
|
@Param({"Sun, 27 Jan 2016 19:18:46 GMT", "Sun, 27 Dec 2016 19:18:46 GMT"})
|
||||||
|
String DATE_STRING;
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public Date parseHttpHeaderDateFormatterNew() {
|
||||||
|
return DateFormatter.parseHttpDate(DATE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@Benchmark
|
||||||
|
public Date parseHttpHeaderDateFormatter() {
|
||||||
|
return DateFormatterOld.parseHttpDate(DATE_STRING);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Benchmark (DATE_STRING) Mode Cnt Score Error Units
|
||||||
|
* parseHttpHeaderDateFormatter Sun, 27 Jan 2016 19:18:46 GMT thrpt 6 4142781.221 ± 82155.002 ops/s
|
||||||
|
* parseHttpHeaderDateFormatter Sun, 27 Dec 2016 19:18:46 GMT thrpt 6 3781810.558 ± 38679.061 ops/s
|
||||||
|
* parseHttpHeaderDateFormatterNew Sun, 27 Jan 2016 19:18:46 GMT thrpt 6 4372569.705 ± 30257.537 ops/s
|
||||||
|
* parseHttpHeaderDateFormatterNew Sun, 27 Dec 2016 19:18:46 GMT thrpt 6 4339785.100 ± 57542.660 ops/s
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*Old DateFormatter.tryParseMonth method:
|
||||||
|
private boolean tryParseMonth(CharSequence txt, int tokenStart, int tokenEnd) {
|
||||||
|
int len = tokenEnd - tokenStart;
|
||||||
|
|
||||||
|
if (len != 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchMonth("Jan", txt, tokenStart)) {
|
||||||
|
month = Calendar.JANUARY;
|
||||||
|
} else if (matchMonth("Feb", txt, tokenStart)) {
|
||||||
|
month = Calendar.FEBRUARY;
|
||||||
|
} else if (matchMonth("Mar", txt, tokenStart)) {
|
||||||
|
month = Calendar.MARCH;
|
||||||
|
} else if (matchMonth("Apr", txt, tokenStart)) {
|
||||||
|
month = Calendar.APRIL;
|
||||||
|
} else if (matchMonth("May", txt, tokenStart)) {
|
||||||
|
month = Calendar.MAY;
|
||||||
|
} else if (matchMonth("Jun", txt, tokenStart)) {
|
||||||
|
month = Calendar.JUNE;
|
||||||
|
} else if (matchMonth("Jul", txt, tokenStart)) {
|
||||||
|
month = Calendar.JULY;
|
||||||
|
} else if (matchMonth("Aug", txt, tokenStart)) {
|
||||||
|
month = Calendar.AUGUST;
|
||||||
|
} else if (matchMonth("Sep", txt, tokenStart)) {
|
||||||
|
month = Calendar.SEPTEMBER;
|
||||||
|
} else if (matchMonth("Oct", txt, tokenStart)) {
|
||||||
|
month = Calendar.OCTOBER;
|
||||||
|
} else if (matchMonth("Nov", txt, tokenStart)) {
|
||||||
|
month = Calendar.NOVEMBER;
|
||||||
|
} else if (matchMonth("Dec", txt, tokenStart)) {
|
||||||
|
month = Calendar.DECEMBER;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user