Skip to content

Commit

Permalink
revisit Date._strptime internals; some less String char copy-ing
Browse files Browse the repository at this point in the history
doesn't have noticeable effect in micro bm; but at least allocates less
kares committed Jul 11, 2017
1 parent 36e97c6 commit 7b8221e
Showing 2 changed files with 45 additions and 44 deletions.
62 changes: 33 additions & 29 deletions core/src/main/java/org/jruby/util/RubyDateFormatter.java
Original file line number Diff line number Diff line change
@@ -50,7 +50,6 @@
import org.joda.time.DateTime;
import org.joda.time.chrono.GJChronology;
import org.joda.time.chrono.JulianChronology;
import org.jruby.RubyEncoding;
import org.jruby.RubyString;
import org.jruby.RubyTime;
import org.jruby.lexer.StrftimeLexer;
@@ -63,8 +62,8 @@ public class RubyDateFormatter {
private static final DateFormatSymbols FORMAT_SYMBOLS = new DateFormatSymbols(Locale.US);
private static final Token[] CONVERSION2TOKEN = new Token[256];

private ThreadContext context;
private StrftimeLexer lexer;
private final ThreadContext context;
private final StrftimeLexer lexer;

static enum Format {
/** encoding to give to output */
@@ -154,7 +153,7 @@ static enum Format {
CONVERSION2TOKEN[alias] = CONVERSION2TOKEN[conversion];
}
}
static final Format INSTANTIATE_ENUM = Format.FORMAT_WEEK_LONG;
//static final Format INSTANTIATE_ENUM = Format.FORMAT_WEEK_LONG;

public static void main(String[] args) {
// composed + special, keys of the switch below
@@ -231,7 +230,7 @@ public RubyDateFormatter(ThreadContext context) {
lexer = new StrftimeLexer((Reader) null);
}

private void addToPattern(List<Token> compiledPattern, String str) {
private static void addToPattern(List<Token> compiledPattern, String str) {
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
@@ -347,8 +346,8 @@ static enum FieldType {
NUMERIC5('0', 5),
TEXT(' ', 0);

char defaultPadder;
int defaultWidth;
final char defaultPadder;
final int defaultWidth;
FieldType(char padder, int width) {
defaultPadder = padder;
defaultWidth = width;
@@ -373,7 +372,7 @@ public ByteList formatToByteList(List<Token> compiledPattern, DateTime dt, long
ByteList toAppendTo = new ByteList();

for (Token token: compiledPattern) {
String output = null;
CharSequence output = null;
long value = 0;
FieldType type = TEXT;
Format format = token.getFormat();
@@ -511,24 +510,27 @@ public ByteList formatToByteList(List<Token> compiledPattern, DateTime dt, long

output = RubyTimeOutputFormatter.formatNumber(dt.getMillisOfSecond(), 3, '0');
if (width > 3) {
StringBuilder buff = new StringBuilder(output.length() + 6).append(output);
if (sub_millis == null || sub_millis.isNil()) { // Time
output += RubyTimeOutputFormatter.formatNumber(nsec, 6, '0');
buff.append(RubyTimeOutputFormatter.formatNumber(nsec, 6, '0'));
} else { // Date, DateTime
int prec = width - 3;
IRubyObject power = context.runtime.newFixnum(10).callMethod("**", context.runtime.newFixnum(prec));
IRubyObject truncated = sub_millis.callMethod(context, "numerator").callMethod(context, "*", power);
truncated = truncated.callMethod(context, "/", sub_millis.callMethod(context, "denominator"));
long decimals = truncated.convertToInteger().getLongValue();
output += RubyTimeOutputFormatter.formatNumber(decimals, prec, '0');
buff.append(RubyTimeOutputFormatter.formatNumber(decimals, prec, '0'));
}
output = buff;
}

if (width < output.length()) {
output = output.substring(0, width);
output = output.subSequence(0, width);
} else {
StringBuilder buff = new StringBuilder(width).append(output);
// Not enough precision, fill with 0
while(output.length() < width)
output += "0";
while (buff.length() < width) buff.append('0');
output = buff;
}
formatter = RubyTimeOutputFormatter.DEFAULT_FORMATTER; // no more formatting
break;
@@ -549,16 +551,17 @@ public ByteList formatToByteList(List<Token> compiledPattern, DateTime dt, long
throw new Error("FORMAT_SPECIAL is a special token only for the lexer.");
}

final String formatted;
try {
output = formatter.format(output, value, type);
formatted = formatter.format(output, value, type);
} catch (IndexOutOfBoundsException ioobe) {
throw context.runtime.newErrnoFromErrno(Errno.ERANGE, "strftime");
}

// reset formatter
formatter = RubyTimeOutputFormatter.DEFAULT_FORMATTER;

toAppendTo.append(output.getBytes(context.runtime.getEncodingService().charsetForEncoding(toAppendTo.getEncoding())));
toAppendTo.append(formatted.getBytes(context.runtime.getEncodingService().charsetForEncoding(toAppendTo.getEncoding())));
}

return toAppendTo;
@@ -568,7 +571,7 @@ public ByteList formatToByteList(List<Token> compiledPattern, DateTime dt, long
* Ruby always follows Astronomical year numbering,
* that is BC x is -x+1 and there is a year 0 (BC 1)
* but Joda-time returns -x for year x BC in Julian chronology (no year 0) */
private int year(DateTime dt, int year) {
private static int year(DateTime dt, int year) {
Chronology c;
if (year < 0 && (
(c = dt.getChronology()) instanceof JulianChronology ||
@@ -577,7 +580,7 @@ private int year(DateTime dt, int year) {
return year;
}

private int formatWeekYear(DateTime dt, int firstDayOfWeek) {
private static int formatWeekYear(DateTime dt, int firstDayOfWeek) {
java.util.Calendar dtCalendar = dt.toGregorianCalendar();
dtCalendar.setFirstDayOfWeek(firstDayOfWeek);
dtCalendar.setMinimalDaysInFirstWeek(7);
@@ -592,7 +595,7 @@ private int formatWeekYear(DateTime dt, int firstDayOfWeek) {
return value;
}

private String formatZone(int colons, int value, RubyTimeOutputFormatter formatter) {
private static StringBuilder formatZone(int colons, int value, RubyTimeOutputFormatter formatter) {
int seconds = Math.abs(value);
int hours = seconds / 3600;
seconds %= 3600;
@@ -608,7 +611,7 @@ private String formatZone(int colons, int value, RubyTimeOutputFormatter formatt

char padder = formatter.getPadder('0');
int defaultWidth = -1;
String after = null;
CharSequence after = null;

switch (colons) {
case 0: // %z -> +hhmm
@@ -617,17 +620,17 @@ private String formatZone(int colons, int value, RubyTimeOutputFormatter formatt
break;
case 1: // %:z -> +hh:mm
defaultWidth = 6;
after = ":" + mm;
after = new StringBuilder(mm.length() + 1).append(':').append(mm);
break;
case 2: // %::z -> +hh:mm:ss
defaultWidth = 9;
after = ":" + mm + ":" + ss;
after = new StringBuilder(mm.length() + ss.length() + 2).append(':').append(mm).append(':').append(ss);
break;
case 3: // %:::z -> +hh[:mm[:ss]]
StringBuilder sb = new StringBuilder();
if (minutes != 0 || seconds != 0) sb.append(":").append(mm);
if (seconds != 0) sb.append(":").append(ss);
after = sb.toString();
StringBuilder sb = new StringBuilder(mm.length() + ss.length() + 2);
if (minutes != 0 || seconds != 0) sb.append(':').append(mm);
if (seconds != 0) sb.append(':').append(ss);
after = sb;
defaultWidth = after.length() + 3;
break;
}
@@ -638,11 +641,12 @@ private String formatZone(int colons, int value, RubyTimeOutputFormatter formatt
width = minWidth;
}
width -= after.length();
String before = RubyTimeOutputFormatter.formatSignedNumber(hours, width, padder);
CharSequence before = RubyTimeOutputFormatter.formatSignedNumber(hours, width, padder);

if (value < 0 && hours == 0) // the formatter could not handle this case
before = before.replace('+', '-');
return before + after;
if (value < 0 && hours == 0) { // the formatter could not handle this case
before = before.toString().replace('+', '-');
}
return new StringBuilder(before.length() + after.length()).append(before).append(after); // before + after
}

/**
27 changes: 12 additions & 15 deletions core/src/main/java/org/jruby/util/RubyTimeOutputFormatter.java
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ public char getPadder(char defaultPadder) {
return padder;
}

public String format(String sequence, long value, FieldType type) {
public String format(CharSequence sequence, long value, FieldType type) {
int width = getWidth(type.defaultWidth);
char padder = getPadder(type.defaultPadder);

@@ -79,31 +79,30 @@ public String format(String sequence, long value, FieldType type) {
for (int i = 0; i < flags.length(); i++) {
switch (flags.charAt(i)) {
case '^':
sequence = sequence.toUpperCase();
sequence = sequence.toString().toUpperCase();
break;
case '#': // change case
char last = sequence.charAt(sequence.length() - 1);
if (Character.isLowerCase(last)) {
sequence = sequence.toUpperCase();
sequence = sequence.toString().toUpperCase();
} else {
sequence = sequence.toLowerCase();
sequence = sequence.toString().toLowerCase();
}
break;
}
}

return sequence;
return sequence.toString();
}

static String formatNumber(long value, int width, char padder) {
if (value >= 0 || padder != '0') {
return padding(Long.toString(value), width, padder);
} else {
return "-" + padding(Long.toString(-value), width - 1, padder);
return padding(Long.toString(value), width, padder).toString();
}
return "-" + padding(Long.toString(-value), width - 1, padder);
}

static String formatSignedNumber(long value, int width, char padder) {
static CharSequence formatSignedNumber(long value, int width, char padder) {
if (padder == '0') {
if (value >= 0) {
return "+" + padding(Long.toString(value), width - 1, padder);
@@ -112,7 +111,7 @@ static String formatSignedNumber(long value, int width, char padder) {
}
} else {
if (value >= 0) {
return padding("+" + Long.toString(value), width, padder);
return padding('+' + Long.toString(value), width, padder);
} else {
return padding(Long.toString(value), width, padder);
}
@@ -121,10 +120,8 @@ static String formatSignedNumber(long value, int width, char padder) {

private static final int SMALLBUF = 100;

static String padding(String sequence, int width, char padder) {
if (sequence.length() >= width) {
return sequence;
}
private static CharSequence padding(CharSequence sequence, int width, char padder) {
if (sequence.length() >= width) return sequence;

if (width > SMALLBUF) throw new IndexOutOfBoundsException("padding width " + width + " too large");

@@ -133,6 +130,6 @@ static String padding(String sequence, int width, char padder) {
buf.append(padder);
}
buf.append(sequence);
return buf.toString();
return buf;
}
}

0 comments on commit 7b8221e

Please sign in to comment.