optimize distance based based calcs by reusing source location computation

This commit is contained in:
Shay Banon 2011-08-19 04:25:36 +03:00
parent d371619dd8
commit 4bacebe860
8 changed files with 295 additions and 66 deletions

View File

@ -0,0 +1,151 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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 org.elasticsearch.benchmark.search.geo;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.unit.SizeValue;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.search.geo.GeoDistance;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
/**
*/
public class GeoDistanceSearchBenchmark {
public static void main(String[] args) throws Exception {
Node node = NodeBuilder.nodeBuilder().node();
Client client = node.client();
ClusterHealthResponse clusterHealthResponse = client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
if (clusterHealthResponse.timedOut()) {
System.err.println("Failed to wait for green status, bailing");
System.exit(1);
}
final long NUM_DOCS = SizeValue.parseSizeValue("1m").singles();
final long NUM_WARM = 50;
final long NUM_RUNS = 100;
if (client.admin().indices().prepareExists("test").execute().actionGet().exists()) {
System.out.println("Found an index, count: " + client.prepareCount("test").setQuery(QueryBuilders.matchAllQuery()).execute().actionGet().count());
} else {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location").field("type", "geo_point").field("lat_lon", true).endObject().endObject()
.endObject().endObject().string();
client.admin().indices().prepareCreate("test")
.setSettings(ImmutableSettings.settingsBuilder().put("number_of_shards", 1).put("number_of_replicas", 0))
.addMapping("type1", mapping)
.execute().actionGet();
System.err.println("--> Indexing [" + NUM_DOCS + "]");
for (long i = 0; i < NUM_DOCS; ) {
client.prepareIndex("test", "type1", Long.toString(i++)).setSource(jsonBuilder().startObject()
.field("name", "New York")
.startObject("location").field("lat", 40.7143528).field("lon", -74.0059731).endObject()
.endObject()).execute().actionGet();
// to NY: 5.286 km
client.prepareIndex("test", "type1", Long.toString(i++)).setSource(jsonBuilder().startObject()
.field("name", "Times Square")
.startObject("location").field("lat", 40.759011).field("lon", -73.9844722).endObject()
.endObject()).execute().actionGet();
// to NY: 0.4621 km
client.prepareIndex("test", "type1", Long.toString(i++)).setSource(jsonBuilder().startObject()
.field("name", "Tribeca")
.startObject("location").field("lat", 40.718266).field("lon", -74.007819).endObject()
.endObject()).execute().actionGet();
// to NY: 1.258 km
client.prepareIndex("test", "type1", Long.toString(i++)).setSource(jsonBuilder().startObject()
.field("name", "Soho")
.startObject("location").field("lat", 40.7247222).field("lon", -74).endObject()
.endObject()).execute().actionGet();
// to NY: 8.572 km
client.prepareIndex("test", "type1", Long.toString(i++)).setSource(jsonBuilder().startObject()
.field("name", "Brooklyn")
.startObject("location").field("lat", 40.65).field("lon", -73.95).endObject()
.endObject()).execute().actionGet();
if ((i % 10000) == 0) {
System.err.println("--> indexed " + i);
}
}
System.err.println("Done indexed");
client.admin().indices().prepareFlush("test").execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
}
System.err.println("--> Warming up (ARC)");
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_WARM; i++) {
run(client, GeoDistance.ARC);
}
long totalTime = System.currentTimeMillis() - start;
System.out.println("--> Warmup (ARC) " + (totalTime / NUM_WARM) + "ms");
System.err.println("--> Perf (ARC)");
start = System.currentTimeMillis();
for (int i = 0; i < NUM_RUNS; i++) {
run(client, GeoDistance.ARC);
}
totalTime = System.currentTimeMillis() - start;
System.out.println("--> Perf (ARC) " + (totalTime / NUM_RUNS) + "ms");
System.err.println("--> Warming up (PLANE)");
start = System.currentTimeMillis();
for (int i = 0; i < NUM_WARM; i++) {
run(client, GeoDistance.PLANE);
}
totalTime = System.currentTimeMillis() - start;
System.out.println("--> Warmup (PLANE) " + (totalTime / NUM_WARM) + "ms");
System.err.println("--> Perf (PLANE)");
start = System.currentTimeMillis();
for (int i = 0; i < NUM_RUNS; i++) {
run(client, GeoDistance.PLANE);
}
totalTime = System.currentTimeMillis() - start;
System.out.println("--> Perf (PLANE) " + (totalTime / NUM_RUNS) + "ms");
node.close();
}
public static void run(Client client, GeoDistance geoDistance) {
client.prepareSearch() // from NY
.setSearchType(SearchType.COUNT)
.setQuery(filteredQuery(matchAllQuery(), geoDistanceFilter("location")
.distance("2km")
.geoDistance(geoDistance)
.point(40.7143528, -74.0059731)))
.execute().actionGet();
}
}

View File

@ -41,6 +41,10 @@ public enum GeoDistance {
@Override public double normalize(double distance, DistanceUnit unit) {
return distance;
}
@Override public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new PlaneFixedSourceDistance(sourceLatitude, sourceLongitude, unit);
}
},
/**
* Calculates distance factor.
@ -57,6 +61,10 @@ public enum GeoDistance {
@Override public double normalize(double distance, DistanceUnit unit) {
return Math.cos(distance / unit.getEarthRadius());
}
@Override public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new FactorFixedSourceDistance(sourceLatitude, sourceLongitude, unit);
}
},
/**
* Calculates distance as points in a globe.
@ -81,12 +89,18 @@ public enum GeoDistance {
@Override public double normalize(double distance, DistanceUnit unit) {
return distance;
}
@Override public FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
return new ArcFixedSourceDistance(sourceLatitude, sourceLongitude, unit);
}
};
public abstract double normalize(double distance, DistanceUnit unit);
public abstract double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit);
public abstract FixedSourceDistance fixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit);
public static GeoDistance fromString(String s) {
if ("plane".equals(s)) {
return PLANE;
@ -97,4 +111,91 @@ public enum GeoDistance {
}
throw new ElasticSearchIllegalArgumentException("No geo distance for [" + s + "]");
}
public static interface FixedSourceDistance {
double calculate(double targetLatitude, double targetLongitude);
}
public static class PlaneFixedSourceDistance implements FixedSourceDistance {
private final double sourceLatitude;
private final double sourceLongitude;
private final double distancePerDegree;
public PlaneFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
this.sourceLatitude = sourceLatitude;
this.sourceLongitude = sourceLongitude;
this.distancePerDegree = unit.getDistancePerDegree();
}
@Override public double calculate(double targetLatitude, double targetLongitude) {
double px = targetLongitude - sourceLongitude;
double py = targetLatitude - sourceLatitude;
return Math.sqrt(px * px + py * py) * distancePerDegree;
}
}
public static class FactorFixedSourceDistance implements FixedSourceDistance {
private final double sourceLatitude;
private final double sourceLongitude;
private final double earthRadius;
private final double a;
private final double sinA;
private final double cosA;
public FactorFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
this.sourceLatitude = sourceLatitude;
this.sourceLongitude = sourceLongitude;
this.earthRadius = unit.getEarthRadius();
this.a = Math.toRadians(90D - sourceLatitude);
this.sinA = Math.sin(a);
this.cosA = Math.cos(a);
}
@Override public double calculate(double targetLatitude, double targetLongitude) {
// TODO: we might want to normalize longitude as we did in LatLng...
double longitudeDifference = targetLongitude - sourceLongitude;
double c = Math.toRadians(90D - targetLatitude);
return (cosA * Math.cos(c)) + (sinA * Math.sin(c) * Math.cos(Math.toRadians(longitudeDifference)));
}
}
public static class ArcFixedSourceDistance implements FixedSourceDistance {
private final double sourceLatitude;
private final double sourceLongitude;
private final double earthRadius;
private final double a;
private final double sinA;
private final double cosA;
public ArcFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {
this.sourceLatitude = sourceLatitude;
this.sourceLongitude = sourceLongitude;
this.earthRadius = unit.getEarthRadius();
this.a = Math.toRadians(90D - sourceLatitude);
this.sinA = Math.sin(a);
this.cosA = Math.cos(a);
}
@Override public double calculate(double targetLatitude, double targetLongitude) {
// TODO: we might want to normalize longitude as we did in LatLng...
double longitudeDifference = targetLongitude - sourceLongitude;
double c = Math.toRadians(90D - targetLatitude);
double factor = (cosA * Math.cos(c)) + (sinA * Math.sin(c) * Math.cos(Math.toRadians(longitudeDifference)));
if (factor < -1D) {
return Math.PI * earthRadius;
} else if (factor >= 1D) {
return 0;
} else {
return Math.acos(factor) * earthRadius;
}
}
}
}

View File

@ -91,6 +91,7 @@ public class GeoDistanceDataComparator extends FieldComparator {
protected final DistanceUnit unit;
protected final GeoDistance geoDistance;
protected final GeoDistance.FixedSourceDistance fixedSourceDistance;
protected final FieldDataCache fieldDataCache;
@ -111,6 +112,8 @@ public class GeoDistanceDataComparator extends FieldComparator {
this.geoDistance = geoDistance;
this.fieldDataCache = fieldDataCache;
this.fixedSourceDistance = geoDistance.fixedSourceDistance(lat, lon, unit);
FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName);
if (mapper == null) {
throw new ElasticSearchIllegalArgumentException("No mapping found for field [" + fieldName + "] for geo distance sort");
@ -143,7 +146,7 @@ public class GeoDistanceDataComparator extends FieldComparator {
// is this true? push this to the "end"
distance = Double.MAX_VALUE;
} else {
distance = geoDistance.calculate(lat, lon, fieldData.latValue(doc), fieldData.lonValue(doc), unit);
distance = fixedSourceDistance.calculate(fieldData.latValue(doc), fieldData.lonValue(doc));
}
final double v2 = distance;
if (bottom > v2) {
@ -161,7 +164,7 @@ public class GeoDistanceDataComparator extends FieldComparator {
// is this true? push this to the "end"
distance = Double.MAX_VALUE;
} else {
distance = geoDistance.calculate(lat, lon, fieldData.latValue(doc), fieldData.lonValue(doc), unit);
distance = fixedSourceDistance.calculate(fieldData.latValue(doc), fieldData.lonValue(doc));
}
values[slot] = distance;
}

View File

@ -47,6 +47,8 @@ public class GeoDistanceFilter extends Filter {
private final FieldDataCache fieldDataCache;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
public GeoDistanceFilter(double lat, double lon, double distance, GeoDistance geoDistance, String fieldName, FieldDataCache fieldDataCache) {
this.lat = lat;
this.lon = lon;
@ -54,6 +56,8 @@ public class GeoDistanceFilter extends Filter {
this.geoDistance = geoDistance;
this.fieldName = fieldName;
this.fieldDataCache = fieldDataCache;
this.fixedSourceDistance = geoDistance.fixedSourceDistance(lat, lon, DistanceUnit.MILES);
}
public double lat() {
@ -78,7 +82,7 @@ public class GeoDistanceFilter extends Filter {
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
final GeoPointFieldData fieldData = (GeoPointFieldData) fieldDataCache.cache(GeoPointFieldDataType.TYPE, reader, fieldName);
return new GeoDistanceDocSet(reader.maxDoc(), geoDistance, fieldData, lat, lon, distance);
return new GeoDistanceDocSet(reader.maxDoc(), fieldData, fixedSourceDistance, distance);
}
@Override
@ -113,18 +117,14 @@ public class GeoDistanceFilter extends Filter {
}
public static class GeoDistanceDocSet extends GetDocSet {
private final GeoDistance geoDistance;
private final double distance; // in miles
private final GeoPointFieldData fieldData;
private final double lat;
private final double lon;
private final double distance;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
public GeoDistanceDocSet(int maxDoc, GeoDistance geoDistance, GeoPointFieldData fieldData, double lat, double lon, double distance) {
public GeoDistanceDocSet(int maxDoc, GeoPointFieldData fieldData, GeoDistance.FixedSourceDistance fixedSourceDistance, double distance) {
super(maxDoc);
this.geoDistance = geoDistance;
this.fieldData = fieldData;
this.lat = lat;
this.lon = lon;
this.fixedSourceDistance = fixedSourceDistance;
this.distance = distance;
}
@ -144,14 +144,14 @@ public class GeoDistanceFilter extends Filter {
double[] lats = fieldData.latValues(doc);
double[] lons = fieldData.lonValues(doc);
for (int i = 0; i < lats.length; i++) {
double d = geoDistance.calculate(lat, lon, lats[i], lons[i], DistanceUnit.MILES);
double d = fixedSourceDistance.calculate(lats[i], lons[i]);
if (d < distance) {
return true;
}
}
return false;
} else {
double d = geoDistance.calculate(lat, lon, fieldData.latValue(doc), fieldData.lonValue(doc), DistanceUnit.MILES);
double d = fixedSourceDistance.calculate(fieldData.latValue(doc), fieldData.lonValue(doc));
return d < distance;
}
}

View File

@ -43,6 +43,7 @@ public class GeoDistanceRangeFilter extends Filter {
private final double inclusiveUpperPoint; // in miles
private final GeoDistance geoDistance;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
private final String fieldName;
@ -55,6 +56,8 @@ public class GeoDistanceRangeFilter extends Filter {
this.fieldName = fieldName;
this.fieldDataCache = fieldDataCache;
this.fixedSourceDistance = geoDistance.fixedSourceDistance(lat, lon, DistanceUnit.MILES);
if (lowerVal != null) {
double f = lowerVal.doubleValue();
long i = NumericUtils.doubleToSortableLong(f);
@ -89,7 +92,7 @@ public class GeoDistanceRangeFilter extends Filter {
@Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
final GeoPointFieldData fieldData = (GeoPointFieldData) fieldDataCache.cache(GeoPointFieldDataType.TYPE, reader, fieldName);
return new GeoDistanceRangeDocSet(reader.maxDoc(), fieldData, geoDistance, lat, lon, inclusiveLowerPoint, inclusiveUpperPoint);
return new GeoDistanceRangeDocSet(reader.maxDoc(), fieldData, fixedSourceDistance, inclusiveLowerPoint, inclusiveUpperPoint);
}
@Override
@ -129,18 +132,14 @@ public class GeoDistanceRangeFilter extends Filter {
public static class GeoDistanceRangeDocSet extends GetDocSet {
private final GeoPointFieldData fieldData;
private final GeoDistance geoDistance;
private final double lat;
private final double lon;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
private final double inclusiveLowerPoint; // in miles
private final double inclusiveUpperPoint; // in miles
public GeoDistanceRangeDocSet(int maxDoc, GeoPointFieldData fieldData, GeoDistance geoDistance, double lat, double lon, double inclusiveLowerPoint, double inclusiveUpperPoint) {
public GeoDistanceRangeDocSet(int maxDoc, GeoPointFieldData fieldData, GeoDistance.FixedSourceDistance fixedSourceDistance, double inclusiveLowerPoint, double inclusiveUpperPoint) {
super(maxDoc);
this.fieldData = fieldData;
this.geoDistance = geoDistance;
this.lat = lat;
this.lon = lon;
this.fixedSourceDistance = fixedSourceDistance;
this.inclusiveLowerPoint = inclusiveLowerPoint;
this.inclusiveUpperPoint = inclusiveUpperPoint;
}
@ -161,14 +160,14 @@ public class GeoDistanceRangeFilter extends Filter {
double[] lats = fieldData.latValues(doc);
double[] lons = fieldData.lonValues(doc);
for (int i = 0; i < lats.length; i++) {
double d = geoDistance.calculate(lat, lon, lats[i], lons[i], DistanceUnit.MILES);
double d = fixedSourceDistance.calculate(lats[i], lons[i]);
if (d >= inclusiveLowerPoint && d <= inclusiveUpperPoint) {
return true;
}
}
return false;
} else {
double d = geoDistance.calculate(lat, lon, fieldData.latValue(doc), fieldData.lonValue(doc), DistanceUnit.MILES);
double d = fixedSourceDistance.calculate(fieldData.latValue(doc), fieldData.lonValue(doc));
if (d >= inclusiveLowerPoint && d <= inclusiveUpperPoint) {
return true;
}

View File

@ -47,6 +47,7 @@ public class GeoDistanceFacetCollector extends AbstractFacetCollector {
protected final DistanceUnit unit;
protected final GeoDistance geoDistance;
protected final GeoDistance.FixedSourceDistance fixedSourceDistance;
protected final FieldDataCache fieldDataCache;
@ -66,6 +67,8 @@ public class GeoDistanceFacetCollector extends AbstractFacetCollector {
this.geoDistance = geoDistance;
this.fieldDataCache = context.fieldDataCache();
this.fixedSourceDistance = geoDistance.fixedSourceDistance(lat, lon, unit);
MapperService.SmartNameFieldMappers smartMappers = context.mapperService().smartName(fieldName);
if (smartMappers == null || !smartMappers.hasMapper()) {
throw new FacetPhaseExecutionException(facetName, "No mapping found for field [" + fieldName + "]");
@ -80,7 +83,7 @@ public class GeoDistanceFacetCollector extends AbstractFacetCollector {
}
this.indexFieldName = smartMappers.mapper().names().indexName();
this.aggregator = new Aggregator(lat, lon, geoDistance, unit, entries);
this.aggregator = new Aggregator(fixedSourceDistance, entries);
}
@Override protected void doSetNextReader(IndexReader reader, int docBase) throws IOException {
@ -100,26 +103,17 @@ public class GeoDistanceFacetCollector extends AbstractFacetCollector {
public static class Aggregator implements GeoPointFieldData.ValueInDocProc {
protected final double lat;
protected final double lon;
private final GeoDistance geoDistance;
private final DistanceUnit unit;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
private final GeoDistanceFacet.Entry[] entries;
public Aggregator(double lat, double lon, GeoDistance geoDistance, DistanceUnit unit, GeoDistanceFacet.Entry[] entries) {
this.lat = lat;
this.lon = lon;
this.geoDistance = geoDistance;
this.unit = unit;
public Aggregator(GeoDistance.FixedSourceDistance fixedSourceDistance, GeoDistanceFacet.Entry[] entries) {
this.fixedSourceDistance = fixedSourceDistance;
this.entries = entries;
}
@Override public void onValue(int docId, double lat, double lon) {
double distance = geoDistance.calculate(this.lat, this.lon, lat, lon, unit);
double distance = fixedSourceDistance.calculate(lat, lon);
for (GeoDistanceFacet.Entry entry : entries) {
if (entry.foundInDoc) {
continue;

View File

@ -45,7 +45,7 @@ public class ScriptGeoDistanceFacetCollector extends GeoDistanceFacetCollector {
super(facetName, fieldName, lat, lon, unit, geoDistance, entries, context);
this.script = context.scriptService().search(context.lookup(), scriptLang, script, params);
this.aggregator = new Aggregator(lat, lon, geoDistance, unit, entries);
this.aggregator = new Aggregator(fixedSourceDistance, entries);
this.scriptAggregator = (Aggregator) this.aggregator;
}
@ -66,28 +66,19 @@ public class ScriptGeoDistanceFacetCollector extends GeoDistanceFacetCollector {
public static class Aggregator implements GeoPointFieldData.ValueInDocProc {
protected final double lat;
protected final double lon;
private final GeoDistance geoDistance;
private final DistanceUnit unit;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
private final GeoDistanceFacet.Entry[] entries;
double scriptValue;
public Aggregator(double lat, double lon, GeoDistance geoDistance, DistanceUnit unit, GeoDistanceFacet.Entry[] entries) {
this.lat = lat;
this.lon = lon;
this.geoDistance = geoDistance;
this.unit = unit;
public Aggregator(GeoDistance.FixedSourceDistance fixedSourceDistance, GeoDistanceFacet.Entry[] entries) {
this.fixedSourceDistance = fixedSourceDistance;
this.entries = entries;
}
@Override public void onValue(int docId, double lat, double lon) {
double distance = geoDistance.calculate(this.lat, this.lon, lat, lon, unit);
double distance = fixedSourceDistance.calculate(lat, lon);
for (GeoDistanceFacet.Entry entry : entries) {
if (entry.foundInDoc) {
continue;

View File

@ -50,7 +50,7 @@ public class ValueGeoDistanceFacetCollector extends GeoDistanceFacetCollector {
}
this.indexValueFieldName = valueFieldName;
this.valueFieldDataType = mapper.fieldDataType();
this.aggregator = new Aggregator(lat, lon, geoDistance, unit, entries);
this.aggregator = new Aggregator(fixedSourceDistance, entries);
}
@Override protected void doSetNextReader(IndexReader reader, int docBase) throws IOException {
@ -60,30 +60,20 @@ public class ValueGeoDistanceFacetCollector extends GeoDistanceFacetCollector {
public static class Aggregator implements GeoPointFieldData.ValueInDocProc {
protected final double lat;
protected final double lon;
private final GeoDistance geoDistance;
private final DistanceUnit unit;
private final GeoDistance.FixedSourceDistance fixedSourceDistance;
private final GeoDistanceFacet.Entry[] entries;
NumericFieldData valueFieldData;
final ValueAggregator valueAggregator = new ValueAggregator();
public Aggregator(double lat, double lon, GeoDistance geoDistance, DistanceUnit unit, GeoDistanceFacet.Entry[] entries) {
this.lat = lat;
this.lon = lon;
this.geoDistance = geoDistance;
this.unit = unit;
public Aggregator(GeoDistance.FixedSourceDistance fixedSourceDistance, GeoDistanceFacet.Entry[] entries) {
this.fixedSourceDistance = fixedSourceDistance;
this.entries = entries;
}
@Override public void onValue(int docId, double lat, double lon) {
double distance = geoDistance.calculate(this.lat, this.lon, lat, lon, unit);
double distance = fixedSourceDistance.calculate(lat, lon);
for (GeoDistanceFacet.Entry entry : entries) {
if (entry.foundInDoc) {
continue;