Reduce cost of various toFilter calls. (#2860)

These happen once per segment and so it's better if they don't do
as much work.
This commit is contained in:
Gian Merlino 2016-04-20 13:28:47 -07:00 committed by Fangjin Yang
parent fc91120b54
commit 7d3e55717d
8 changed files with 122 additions and 115 deletions

View File

@ -24,24 +24,19 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.metamx.common.StringUtils; import com.metamx.common.StringUtils;
import io.druid.query.extraction.ExtractionFn; import io.druid.query.extraction.ExtractionFn;
import io.druid.query.lookup.LookupExtractionFn;
import io.druid.query.lookup.LookupExtractor;
import io.druid.segment.filter.InFilter; import io.druid.segment.filter.InFilter;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Set;
public class InDimFilter implements DimFilter public class InDimFilter implements DimFilter
{ {
private final List<String> values; private final ImmutableSortedSet<String> values;
private final String dimension; private final String dimension;
private final ExtractionFn extractionFn; private final ExtractionFn extractionFn;
@ -54,7 +49,7 @@ public class InDimFilter implements DimFilter
{ {
Preconditions.checkNotNull(dimension, "dimension can not be null"); Preconditions.checkNotNull(dimension, "dimension can not be null");
Preconditions.checkArgument(values != null && !values.isEmpty(), "values can not be null or empty"); Preconditions.checkArgument(values != null && !values.isEmpty(), "values can not be null or empty");
this.values = Lists.newArrayList( this.values = ImmutableSortedSet.copyOf(
Iterables.transform( Iterables.transform(
values, new Function<String, String>() values, new Function<String, String>()
{ {
@ -78,7 +73,7 @@ public class InDimFilter implements DimFilter
} }
@JsonProperty @JsonProperty
public List<String> getValues() public Set<String> getValues()
{ {
return values; return values;
} }
@ -103,7 +98,10 @@ public class InDimFilter implements DimFilter
} }
byte[] extractionFnBytes = extractionFn == null ? new byte[0] : extractionFn.getCacheKey(); byte[] extractionFnBytes = extractionFn == null ? new byte[0] : extractionFn.getCacheKey();
ByteBuffer filterCacheKey = ByteBuffer.allocate(3 + dimensionBytes.length + valuesBytesSize + extractionFnBytes.length) ByteBuffer filterCacheKey = ByteBuffer.allocate(3
+ dimensionBytes.length
+ valuesBytesSize
+ extractionFnBytes.length)
.put(DimFilterCacheHelper.IN_CACHE_ID) .put(DimFilterCacheHelper.IN_CACHE_ID)
.put(dimensionBytes) .put(dimensionBytes)
.put(DimFilterCacheHelper.STRING_SEPARATOR) .put(DimFilterCacheHelper.STRING_SEPARATOR)
@ -125,7 +123,7 @@ public class InDimFilter implements DimFilter
@Override @Override
public Filter toFilter() public Filter toFilter()
{ {
return new InFilter(dimension, ImmutableSet.copyOf(values), extractionFn); return new InFilter(dimension, values, extractionFn);
} }
@Override @Override

View File

@ -22,9 +22,13 @@ package io.druid.query.filter;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.metamx.common.StringUtils; import com.metamx.common.StringUtils;
import io.druid.query.extraction.ExtractionFn; import io.druid.query.extraction.ExtractionFn;
import io.druid.segment.filter.JavaScriptFilter; import io.druid.segment.filter.JavaScriptFilter;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptableObject;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -34,6 +38,8 @@ public class JavaScriptDimFilter implements DimFilter
private final String function; private final String function;
private final ExtractionFn extractionFn; private final ExtractionFn extractionFn;
private final JavaScriptPredicate predicate;
@JsonCreator @JsonCreator
public JavaScriptDimFilter( public JavaScriptDimFilter(
@JsonProperty("dimension") String dimension, @JsonProperty("dimension") String dimension,
@ -46,6 +52,7 @@ public class JavaScriptDimFilter implements DimFilter
this.dimension = dimension; this.dimension = dimension;
this.function = function; this.function = function;
this.extractionFn = extractionFn; this.extractionFn = extractionFn;
this.predicate = new JavaScriptPredicate(function, extractionFn);
} }
@JsonProperty @JsonProperty
@ -92,7 +99,7 @@ public class JavaScriptDimFilter implements DimFilter
@Override @Override
public Filter toFilter() public Filter toFilter()
{ {
return new JavaScriptFilter(dimension, function, extractionFn); return new JavaScriptFilter(dimension, predicate);
} }
@Override @Override
@ -135,4 +142,76 @@ public class JavaScriptDimFilter implements DimFilter
result = 31 * result + (extractionFn != null ? extractionFn.hashCode() : 0); result = 31 * result + (extractionFn != null ? extractionFn.hashCode() : 0);
return result; return result;
} }
public static class JavaScriptPredicate implements Predicate<String>
{
final ScriptableObject scope;
final Function fnApply;
final String script;
final ExtractionFn extractionFn;
public JavaScriptPredicate(final String script, final ExtractionFn extractionFn)
{
Preconditions.checkNotNull(script, "script must not be null");
this.script = script;
this.extractionFn = extractionFn;
final Context cx = Context.enter();
try {
cx.setOptimizationLevel(9);
scope = cx.initStandardObjects();
fnApply = cx.compileFunction(scope, script, "script", 1, null);
}
finally {
Context.exit();
}
}
@Override
public boolean apply(final String input)
{
// one and only one context per thread
final Context cx = Context.enter();
try {
return applyInContext(cx, input);
}
finally {
Context.exit();
}
}
public boolean applyInContext(Context cx, String input)
{
if (extractionFn != null) {
input = extractionFn.apply(input);
}
return Context.toBoolean(fnApply.call(cx, scope, scope, new String[]{input}));
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JavaScriptPredicate that = (JavaScriptPredicate) o;
if (!script.equals(that.script)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
return script.hashCode();
}
}
} }

View File

@ -27,6 +27,7 @@ import io.druid.query.extraction.ExtractionFn;
import io.druid.segment.filter.RegexFilter; import io.druid.segment.filter.RegexFilter;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.regex.Pattern;
/** /**
*/ */
@ -36,6 +37,8 @@ public class RegexDimFilter implements DimFilter
private final String pattern; private final String pattern;
private final ExtractionFn extractionFn; private final ExtractionFn extractionFn;
private final Pattern compiledPattern;
@JsonCreator @JsonCreator
public RegexDimFilter( public RegexDimFilter(
@JsonProperty("dimension") String dimension, @JsonProperty("dimension") String dimension,
@ -48,6 +51,7 @@ public class RegexDimFilter implements DimFilter
this.dimension = dimension; this.dimension = dimension;
this.pattern = pattern; this.pattern = pattern;
this.extractionFn = extractionFn; this.extractionFn = extractionFn;
this.compiledPattern = Pattern.compile(pattern);
} }
@JsonProperty @JsonProperty
@ -94,7 +98,7 @@ public class RegexDimFilter implements DimFilter
@Override @Override
public Filter toFilter() public Filter toFilter()
{ {
return new RegexFilter(dimension, pattern, extractionFn); return new RegexFilter(dimension, compiledPattern, extractionFn);
} }
@Override @Override

View File

@ -19,29 +19,27 @@
package io.druid.segment.filter; package io.druid.segment.filter;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.metamx.collections.bitmap.ImmutableBitmap; import com.metamx.collections.bitmap.ImmutableBitmap;
import io.druid.query.extraction.ExtractionFn;
import io.druid.query.filter.BitmapIndexSelector; import io.druid.query.filter.BitmapIndexSelector;
import io.druid.query.filter.Filter; import io.druid.query.filter.Filter;
import io.druid.query.filter.JavaScriptDimFilter;
import io.druid.query.filter.ValueMatcher; import io.druid.query.filter.ValueMatcher;
import io.druid.query.filter.ValueMatcherFactory; import io.druid.query.filter.ValueMatcherFactory;
import org.mozilla.javascript.Context; import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptableObject;
public class JavaScriptFilter implements Filter public class JavaScriptFilter implements Filter
{ {
private final JavaScriptPredicate predicate;
private final String dimension; private final String dimension;
private final ExtractionFn extractionFn; private final JavaScriptDimFilter.JavaScriptPredicate predicate;
public JavaScriptFilter(String dimension, final String script, ExtractionFn extractionFn) public JavaScriptFilter(
String dimension,
JavaScriptDimFilter.JavaScriptPredicate predicate
)
{ {
this.dimension = dimension; this.dimension = dimension;
this.predicate = new JavaScriptPredicate(script, extractionFn); this.predicate = predicate;
this.extractionFn = extractionFn;
} }
@Override @Override
@ -71,76 +69,4 @@ public class JavaScriptFilter implements Filter
// suboptimal, since we need create one context per call to predicate.apply() // suboptimal, since we need create one context per call to predicate.apply()
return factory.makeValueMatcher(dimension, predicate); return factory.makeValueMatcher(dimension, predicate);
} }
static class JavaScriptPredicate implements Predicate<String>
{
final ScriptableObject scope;
final Function fnApply;
final String script;
final ExtractionFn extractionFn;
public JavaScriptPredicate(final String script, final ExtractionFn extractionFn)
{
Preconditions.checkNotNull(script, "script must not be null");
this.script = script;
this.extractionFn = extractionFn;
final Context cx = Context.enter();
try {
cx.setOptimizationLevel(9);
scope = cx.initStandardObjects();
fnApply = cx.compileFunction(scope, script, "script", 1, null);
}
finally {
Context.exit();
}
}
@Override
public boolean apply(final String input)
{
// one and only one context per thread
final Context cx = Context.enter();
try {
return applyInContext(cx, input);
}
finally {
Context.exit();
}
}
public boolean applyInContext(Context cx, String input)
{
if (extractionFn != null) {
input = extractionFn.apply(input);
}
return Context.toBoolean(fnApply.call(cx, scope, scope, new String[]{input}));
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JavaScriptPredicate that = (JavaScriptPredicate) o;
if (!script.equals(that.script)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
return script.hashCode();
}
}
} }

View File

@ -30,7 +30,7 @@ public class RegexFilter extends DimensionPredicateFilter
{ {
public RegexFilter( public RegexFilter(
String dimension, String dimension,
final String pattern, final Pattern pattern,
final ExtractionFn extractionFn final ExtractionFn extractionFn
) )
{ {
@ -38,12 +38,10 @@ public class RegexFilter extends DimensionPredicateFilter
dimension, dimension,
new Predicate<String>() new Predicate<String>()
{ {
Pattern compiled = Pattern.compile(pattern);
@Override @Override
public boolean apply(String input) public boolean apply(String input)
{ {
return (input != null) && compiled.matcher(input).find(); return (input != null) && pattern.matcher(input).find();
} }
}, },
extractionFn extractionFn

View File

@ -36,7 +36,7 @@ public class InDimFilterSerDesrTest
{ {
private static ObjectMapper mapper; private static ObjectMapper mapper;
private final String actualInFilter = "{\"type\":\"in\",\"dimension\":\"dimTest\",\"values\":[\"good\",\"bad\"],\"extractionFn\":null}"; private final String actualInFilter = "{\"type\":\"in\",\"dimension\":\"dimTest\",\"values\":[\"bad\",\"good\"],\"extractionFn\":null}";
@Before @Before
public void setUp() public void setUp()
{ {

View File

@ -27,31 +27,33 @@ import java.util.Arrays;
public class JavaScriptDimFilterTest public class JavaScriptDimFilterTest
{ {
private static final String FN1 = "function(x) { return x }";
private static final String FN2 = "function(x) { return x + x }";
@Test @Test
public void testGetCacheKey() public void testGetCacheKey()
{ {
JavaScriptDimFilter javaScriptDimFilter = new JavaScriptDimFilter("dim", "fn", null); JavaScriptDimFilter javaScriptDimFilter = new JavaScriptDimFilter("dim", FN1, null);
JavaScriptDimFilter javaScriptDimFilter2 = new JavaScriptDimFilter("di", "mfn", null); JavaScriptDimFilter javaScriptDimFilter2 = new JavaScriptDimFilter("di", FN2, null);
Assert.assertFalse(Arrays.equals(javaScriptDimFilter.getCacheKey(), javaScriptDimFilter2.getCacheKey())); Assert.assertFalse(Arrays.equals(javaScriptDimFilter.getCacheKey(), javaScriptDimFilter2.getCacheKey()));
RegexDimExtractionFn regexFn = new RegexDimExtractionFn(".*", false, null); RegexDimExtractionFn regexFn = new RegexDimExtractionFn(".*", false, null);
JavaScriptDimFilter javaScriptDimFilter3 = new JavaScriptDimFilter("dim", "fn", regexFn); JavaScriptDimFilter javaScriptDimFilter3 = new JavaScriptDimFilter("dim", FN1, regexFn);
Assert.assertFalse(Arrays.equals(javaScriptDimFilter.getCacheKey(), javaScriptDimFilter3.getCacheKey())); Assert.assertFalse(Arrays.equals(javaScriptDimFilter.getCacheKey(), javaScriptDimFilter3.getCacheKey()));
} }
@Test @Test
public void testEquals() public void testEquals()
{ {
JavaScriptDimFilter javaScriptDimFilter = new JavaScriptDimFilter("dim", "fn", null); JavaScriptDimFilter javaScriptDimFilter = new JavaScriptDimFilter("dim", FN1, null);
JavaScriptDimFilter javaScriptDimFilter2 = new JavaScriptDimFilter("di", "mfn", null); JavaScriptDimFilter javaScriptDimFilter2 = new JavaScriptDimFilter("di", FN2, null);
JavaScriptDimFilter javaScriptDimFilter3 = new JavaScriptDimFilter("di", "mfn", null); JavaScriptDimFilter javaScriptDimFilter3 = new JavaScriptDimFilter("di", FN2, null);
Assert.assertNotEquals(javaScriptDimFilter, javaScriptDimFilter2); Assert.assertNotEquals(javaScriptDimFilter, javaScriptDimFilter2);
Assert.assertEquals(javaScriptDimFilter2, javaScriptDimFilter3); Assert.assertEquals(javaScriptDimFilter2, javaScriptDimFilter3);
RegexDimExtractionFn regexFn = new RegexDimExtractionFn(".*", false, null); RegexDimExtractionFn regexFn = new RegexDimExtractionFn(".*", false, null);
JavaScriptDimFilter javaScriptDimFilter4 = new JavaScriptDimFilter("dim", "fn", regexFn); JavaScriptDimFilter javaScriptDimFilter4 = new JavaScriptDimFilter("dim", FN1, regexFn);
JavaScriptDimFilter javaScriptDimFilter5 = new JavaScriptDimFilter("dim", "fn", regexFn); JavaScriptDimFilter javaScriptDimFilter5 = new JavaScriptDimFilter("dim", FN1, regexFn);
Assert.assertNotEquals(javaScriptDimFilter, javaScriptDimFilter3); Assert.assertNotEquals(javaScriptDimFilter, javaScriptDimFilter3);
Assert.assertEquals(javaScriptDimFilter4, javaScriptDimFilter5); Assert.assertEquals(javaScriptDimFilter4, javaScriptDimFilter5);
} }
@ -59,15 +61,15 @@ public class JavaScriptDimFilterTest
@Test @Test
public void testHashcode() public void testHashcode()
{ {
JavaScriptDimFilter javaScriptDimFilter = new JavaScriptDimFilter("dim", "fn", null); JavaScriptDimFilter javaScriptDimFilter = new JavaScriptDimFilter("dim", FN1, null);
JavaScriptDimFilter javaScriptDimFilter2 = new JavaScriptDimFilter("di", "mfn", null); JavaScriptDimFilter javaScriptDimFilter2 = new JavaScriptDimFilter("di", FN2, null);
JavaScriptDimFilter javaScriptDimFilter3 = new JavaScriptDimFilter("di", "mfn", null); JavaScriptDimFilter javaScriptDimFilter3 = new JavaScriptDimFilter("di", FN2, null);
Assert.assertNotEquals(javaScriptDimFilter.hashCode(), javaScriptDimFilter2.hashCode()); Assert.assertNotEquals(javaScriptDimFilter.hashCode(), javaScriptDimFilter2.hashCode());
Assert.assertEquals(javaScriptDimFilter2.hashCode(), javaScriptDimFilter3.hashCode()); Assert.assertEquals(javaScriptDimFilter2.hashCode(), javaScriptDimFilter3.hashCode());
RegexDimExtractionFn regexFn = new RegexDimExtractionFn(".*", false, null); RegexDimExtractionFn regexFn = new RegexDimExtractionFn(".*", false, null);
JavaScriptDimFilter javaScriptDimFilter4 = new JavaScriptDimFilter("dim", "fn", regexFn); JavaScriptDimFilter javaScriptDimFilter4 = new JavaScriptDimFilter("dim", FN1, regexFn);
JavaScriptDimFilter javaScriptDimFilter5 = new JavaScriptDimFilter("dim", "fn", regexFn); JavaScriptDimFilter javaScriptDimFilter5 = new JavaScriptDimFilter("dim", FN1, regexFn);
Assert.assertNotEquals(javaScriptDimFilter.hashCode(), javaScriptDimFilter3.hashCode()); Assert.assertNotEquals(javaScriptDimFilter.hashCode(), javaScriptDimFilter3.hashCode());
Assert.assertEquals(javaScriptDimFilter4.hashCode(), javaScriptDimFilter5.hashCode()); Assert.assertEquals(javaScriptDimFilter4.hashCode(), javaScriptDimFilter5.hashCode());
} }

View File

@ -48,7 +48,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class JavascriptFilterTest extends BaseFilterTest public class JavaScriptFilterTest extends BaseFilterTest
{ {
private static final String TIMESTAMP_COLUMN = "timestamp"; private static final String TIMESTAMP_COLUMN = "timestamp";
@ -72,7 +72,7 @@ public class JavascriptFilterTest extends BaseFilterTest
PARSER.parse(ImmutableMap.<String, Object>of("dim0", "5", "dim1", "abc")) PARSER.parse(ImmutableMap.<String, Object>of("dim0", "5", "dim1", "abc"))
); );
public JavascriptFilterTest( public JavaScriptFilterTest(
String testName, String testName,
IndexBuilder indexBuilder, IndexBuilder indexBuilder,
Function<IndexBuilder, Pair<StorageAdapter, Closeable>> finisher, Function<IndexBuilder, Pair<StorageAdapter, Closeable>> finisher,