From 0a7db6a1260b907b68a303f8c2f9f448482776e3 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Mon, 13 Oct 2014 05:20:20 -0400 Subject: [PATCH] Avoid PathBuilder for fast-path path parsing This makes the typical path parse much faster: - avoid Character.isLetter in favor of just ASCII alphanumeric - allow fast path with hyphens and underscores involved - don't use PathBuilder, build a Path directly by traversing the string backward; this avoids some object allocation --- .../java/com/typesafe/config/impl/Parser.java | 57 ++++++++++++------- config/src/test/scala/Profiling.scala | 17 ++++++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/Parser.java b/config/src/main/java/com/typesafe/config/impl/Parser.java index aafca630..221ae9e7 100644 --- a/config/src/main/java/com/typesafe/config/impl/Parser.java +++ b/config/src/main/java/com/typesafe/config/impl/Parser.java @@ -1138,26 +1138,51 @@ final class Parser { } } - // the idea is to see if the string has any chars that might require the - // full parser to deal with. - private static boolean hasUnsafeChars(String s) { - for (int i = 0; i < s.length(); ++i) { + // the idea is to see if the string has any chars or features + // that might require the full parser to deal with. + private static boolean looksUnsafeForFastParser(String s) { + boolean lastWasDot = true; // start of path is also a "dot" + int len = s.length(); + if (s.isEmpty()) + return true; + if (s.charAt(0) == '.') + return true; + if (s.charAt(len - 1) == '.') + return true; + + for (int i = 0; i < len; ++i) { char c = s.charAt(i); - if (Character.isLetter(c) || c == '.') + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { + lastWasDot = false; continue; - else + } else if (c == '.') { + if (lastWasDot) + return true; // ".." means we need to throw an error + lastWasDot = true; + } else if (c == '-') { + if (lastWasDot) + return true; + continue; + } else { return true; + } } + + if (lastWasDot) + return true; + return false; } - private static void appendPathString(PathBuilder pb, String s) { - int splitAt = s.indexOf('.'); + private static Path fastPathBuild(Path tail, String s, int end) { + // lastIndexOf takes last index it should look at, end - 1 not end + int splitAt = s.lastIndexOf('.', end - 1); + // this works even if splitAt is -1; then we start the substring at 0 + Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail); if (splitAt < 0) { - pb.appendKey(s); + return withOneMoreElement; } else { - pb.appendKey(s.substring(0, splitAt)); - appendPathString(pb, s.substring(splitAt + 1)); + return fastPathBuild(withOneMoreElement, s, splitAt); } } @@ -1165,15 +1190,9 @@ final class Parser { // we just have something like "foo" or "foo.bar" private static Path speculativeFastParsePath(String path) { String s = ConfigImplUtil.unicodeTrim(path); - if (s.isEmpty()) + if (looksUnsafeForFastParser(s)) return null; - if (hasUnsafeChars(s)) - return null; - if (s.startsWith(".") || s.endsWith(".") || s.contains("..")) - return null; // let the full parser throw the error - PathBuilder pb = new PathBuilder(); - appendPathString(pb, s); - return pb.result(); + return fastPathBuild(null, s, s.length()); } } diff --git a/config/src/test/scala/Profiling.scala b/config/src/test/scala/Profiling.scala index f1819245..5cd412ef 100644 --- a/config/src/test/scala/Profiling.scala +++ b/config/src/test/scala/Profiling.scala @@ -94,6 +94,23 @@ object GetExistingPath extends App { Util.loop(args, task) } +object GetSeveralExistingPaths extends App { + val conf = ConfigFactory.parseString("aaaaa { bbbbb.ccccc.d=42, qqqqq.rrrrr = 43 }, xxxxx.yyyyy.zzzzz = 44 ").resolve() + + def task() { + if (conf.getInt("aaaaa.bbbbb.ccccc.d") != 42 || + conf.getInt("aaaaa.qqqqq.rrrrr") != 43 || + conf.getInt("xxxxx.yyyyy.zzzzz") != 44) { + throw new Exception("broken get") + } + } + + val ms = Util.time(task, 5000000) + println("GetSeveralExistingPaths: " + ms + "ms") + + Util.loop(args, task) +} + object HasPathOnMissing extends App { val conf = ConfigFactory.parseString("aaaaa.bbbbb.ccccc.d=42,x=10, y=11, z=12").resolve()