Binary search in ScalarArrayOpExpr for OR'd constant arrays

Started by James Colemanover 5 years ago78 messages
#1James Coleman
jtc331@gmail.com
1 attachment(s)

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1]/messages/by-id/CAAaqYe-UQBba7sScrucDOyHb7cDoNbWf_rcLrOWeD4ikP3_qTQ@mail.gmail.com I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

James

[1]: /messages/by-id/CAAaqYe-UQBba7sScrucDOyHb7cDoNbWf_rcLrOWeD4ikP3_qTQ@mail.gmail.com

Attachments:

v1-0001-Binary-search-const-arrays-in-OR-d-ScalarArrayOps.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Binary-search-const-arrays-in-OR-d-ScalarArrayOps.patchDownload
From 08742543d7865d5f25c24c26bf1014924035c9eb Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Fri, 10 Apr 2020 21:40:50 +0000
Subject: [PATCH v1] Binary search const arrays in OR'd ScalarArrayOps

Currently all scalar array op expressions execute as a linear search
through the array argument. However when OR semantics are desired it's
possible to instead use a binary search. Here we apply that optimization
to constant arrays (so we don't need to worry about teaching expression
execution when params change) of at least length 9 (since very short
arrays average to the same number of comparisons for linear searches and
thus avoid the preprocessing necessary for a binary search).
---
 src/backend/executor/execExpr.c           |  79 +++++++--
 src/backend/executor/execExprInterp.c     | 193 ++++++++++++++++++++++
 src/include/executor/execExpr.h           |  14 ++
 src/test/regress/expected/expressions.out |  39 +++++
 src/test/regress/sql/expressions.sql      |  11 ++
 5 files changed, 326 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c6a77bd66f..c202cc7e89 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -49,6 +49,7 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
+#define MIN_ARRAY_SIZE_FOR_BINARY_SEARCH 9
 
 typedef struct LastAttnumInfo
 {
@@ -947,11 +948,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				bool		useBinarySearch = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -964,12 +967,58 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				func = opexpr->opfuncid;
+
+				/*
+				 * If we have a constant array and want OR semantics, then we
+				 * can use a binary search to implement the op instead of
+				 * looping through the entire array for each execution.
+				 */
+				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
+					!((Const *) arrayarg)->constisnull)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					Oid			orderingOp;
+					Oid			orderingFunc;
+					Oid			opfamily;
+					Oid			opcintype;
+					int16		strategy;
+					int			nitems;
+
+					/*
+					 * Only do the optimization if we have a large enough
+					 * array to make it worth it.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
+					{
+						/*
+						 * Find the ordering op that matches the originally
+						 * planned equality op.
+						 */
+						orderingOp = get_ordering_op_for_equality_op(opexpr->opno, NULL);
+						get_ordering_op_properties(orderingOp, &opfamily, &opcintype, &strategy);
+						orderingFunc = get_opfamily_proc(opfamily, opcintype, opcintype, BTORDER_PROC);
+
+						/*
+						 * But we may not have one, so fall back to the
+						 * default implementation if necessary.
+						 */
+						if (OidIsValid(orderingFunc))
+						{
+							useBinarySearch = true;
+							func = orderingFunc;
+						}
+					}
+				}
+
+				InvokeFunctionExecuteHook(func);
+				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -981,18 +1030,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * be overwritten by EEOP_SCALARARRAYOP[_BINSEARCH], and will
+				 * not be passed to any other expression.
 				 */
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				if (useBinarySearch)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_BINSEARCH;
+					scratch.d.scalararraybinsearchop.finfo = finfo;
+					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
+					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
+				}
+				else
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				}
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 113ed1547c..5bebafbf0c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,6 +76,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -177,6 +178,8 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
+static int	compare_array_elements(const void *a, const void *b, void *arg);
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -425,6 +428,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_SCALARARRAYOP_BINSEARCH,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1464,6 +1468,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_BINSEARCH)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpBinSearch(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3581,6 +3593,187 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it binary searches through the array for a
+ * match after sorting the array and removing null and duplicate entries.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR.  Of course we short-circuit as soon as the result
+ * is known.
+ */
+void
+ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		result;
+	bool		resultnull;
+	bool	   *elem_nulls;
+	int			l = 0,
+				r,
+				res;
+
+	/* We don't setup a binary search op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (op->d.scalararraybinsearchop.elem_values == NULL)
+	{
+		/* Cache the original lhs so we can scribble on it. */
+		Datum		scalar = fcinfo->args[0].value;
+		bool		scalar_isnull = fcinfo->args[0].isnull;
+		int			num_nonnulls = 0;
+		MemoryContext old_cxt;
+		MemoryContext array_cxt;
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		array_cxt = AllocSetContextCreate(
+										  econtext->ecxt_per_query_memory,
+										  "scalararraybinsearchop context",
+										  ALLOCSET_SMALL_SIZES);
+		old_cxt = MemoryContextSwitchTo(array_cxt);
+
+		deconstruct_array(arr,
+						  ARR_ELEMTYPE(arr),
+						  typlen, typbyval, typalign,
+						  &op->d.scalararraybinsearchop.elem_values, &elem_nulls, &op->d.scalararraybinsearchop.num_elems);
+
+		/* Remove null entries from the array. */
+		for (int j = 0; j < op->d.scalararraybinsearchop.num_elems; j++)
+		{
+			if (!elem_nulls[j])
+				op->d.scalararraybinsearchop.elem_values[num_nonnulls++] = op->d.scalararraybinsearchop.elem_values[j];
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararraybinsearchop.has_nulls = num_nonnulls < op->d.scalararraybinsearchop.num_elems;
+		op->d.scalararraybinsearchop.num_elems = num_nonnulls;
+
+		/*
+		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
+		 * rhs values are guaranteed to be non-null.
+		 */
+		fcinfo->args[0].isnull = false;
+		fcinfo->args[1].isnull = false;
+
+		/* Sort the array and remove duplicate elements. */
+		qsort_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+				  compare_array_elements, op);
+		op->d.scalararraybinsearchop.num_elems = qunique_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+															 compare_array_elements, op);
+
+		/* Restore the lhs value after we scribbed on it for sorting. */
+		fcinfo->args[0].value = scalar;
+		fcinfo->args[0].isnull = scalar_isnull;
+
+		MemoryContextSwitchTo(old_cxt);
+	}
+
+	/*
+	 * We only setup a binary search op if we have > 8 elements, so we don't
+	 * need to worry about adding an optimization for the empty array case.
+	 */
+	Assert(!(op->d.scalararraybinsearchop.num_elems <= 0 && !op->d.scalararraybinsearchop.has_nulls));
+
+	/* Assume no match will be found until proven otherwise. */
+	result = BoolGetDatum(false);
+	resultnull = false;
+
+	/* Binary search through the array. */
+	r = op->d.scalararraybinsearchop.num_elems - 1;
+	while (l <= r)
+	{
+		int			i = (l + r) / 2;
+
+		fcinfo->args[1].value = op->d.scalararraybinsearchop.elem_values[i];
+
+		/* Call comparison function */
+		fcinfo->isnull = false;
+		res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+
+		if (res == 0)
+		{
+			result = BoolGetDatum(true);
+			resultnull = false;
+			break;
+		}
+		else if (res > 0)
+			l = i + 1;
+		else
+			r = i - 1;
+	}
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararraybinsearchop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/* Had nulls, so strict function implies null. */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* Execute function will null rhs just once. */
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+			result = BoolGetDatum(res == 0);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+compare_array_elements(const void *a, const void *b, void *arg)
+{
+	ExprEvalStep *op = (ExprEvalStep *) arg;
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) a);
+	fcinfo->args[1].value = *((const Datum *) b);
+
+	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index dbe8649a57..ac4478d060 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -213,6 +213,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_SCALARARRAYOP_BINSEARCH,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -550,6 +551,18 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_SCALARARRAYOP_BINSEARCH */
+		struct
+		{
+			int			num_elems;
+			bool		has_nulls;
+			Datum	   *elem_values;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+		}			scalararraybinsearchop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -728,6 +741,7 @@ extern void ExecEvalSubscriptingRefAssign(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 4f4deaec22..55b57b9c59 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,42 @@ select count(*) from date_tbl
     12
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..3cb850d838 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,14 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
-- 
2.17.1

#2Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#1)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

Another idea - would a bloom filter be useful here, as a second
optimization? That is, for large arrays build s small bloom filter,
allowing us to skip even the binary search.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#3James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#2)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Another idea - would a bloom filter be useful here, as a second
optimization? That is, for large arrays build s small bloom filter,
allowing us to skip even the binary search.

That's an interesting idea. I actually haven't personally worked with
bloom filters, so didn't think about that.

Are you thinking that you'd also build the filter *and* presort the
array? Or try to get away with using only the bloom filter and not
expanding and sorting the array at all?

Thanks,
James

#4Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#3)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

I think we usually use something like 64 or so in other places, but
maybe I'm wrong. The current limit 9 seems a bit too low, but I may be
wrong. Let's not obsess about this too much, let's do some experiments
and pick a value based on that.

Another idea - would a bloom filter be useful here, as a second
optimization? That is, for large arrays build s small bloom filter,
allowing us to skip even the binary search.

That's an interesting idea. I actually haven't personally worked with
bloom filters, so didn't think about that.

Are you thinking that you'd also build the filter *and* presort the
array? Or try to get away with using only the bloom filter and not
expanding and sorting the array at all?

Yeah, something like that. My intuition is the bloom filter is useful
only above some number of items, and the number is higher than for the
binary search. So we'd end up with two thresholds, first one enabling
binary search, the second one enabling bloom filter.

Of course, the "unknown" variable here is how often we actually find the
value in the array. If 100% of the queries has a match, then the bloom
filter is a waste of time. If there are no matches, it can make a
significant difference.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#5David Rowley
dgrowleyml@gmail.com
In reply to: Tomas Vondra (#4)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, 24 Apr 2020 at 02:56, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

I think we usually use something like 64 or so in other places, but
maybe I'm wrong. The current limit 9 seems a bit too low, but I may be
wrong. Let's not obsess about this too much, let's do some experiments
and pick a value based on that.

If single comparison for a binary search costs about the same as an
equality check, then wouldn't the crossover point be much lower than
64? The binary search should find or not find the target in log2(N)
rather than N. ceil(log2(9)) is 4, which is of course less than 9.
For 64, it's 6, so are you not just doing a possible 58 equality
checks than necessary? Of course, it's a bit more complex as for
values that *are* in the array, the linear search will, on average,
only check half the values. Assuming that, then 9 does not seem too
far off. I guess benchmarks at various crossover points would speak a
thousand words.

David

#6Andres Freund
andres@anarazel.de
In reply to: David Rowley (#5)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

Hi,

On 2020-04-24 10:09:36 +1200, David Rowley wrote:

If single comparison for a binary search costs about the same as an
equality check, then wouldn't the crossover point be much lower than
64?

The costs aren't quite as simple as that though. Binary search usually
has issues with cache misses: In contrast to linear accesses each step
will be a cache miss, as the address is not predictable; and even if the
CPU couldn't predict accesses in the linear search case, often multiple
entries fit on a single cache line. Additionally out-of-order execution
is usually a lot more effective for linear searches (e.g. the next
elements can be compared before the current one is finished if that's
what the branch predictor says is likely).

Greetings,

Andres Freund

#7James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#4)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, Apr 23, 2020 at 10:55 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

Well since it has to do preprocessing work (expanding the array and
then sorting it), then the number of rows processed matters, right?
For example, doing a linear search on 1000 items only once is going to
be cheaper than preprocessing the array and then doing a binary
search, but only a very large row count the binary search +
preprocessing might very well win out for only a 10 element array.

I'm not trying to argue for more work for myself here: I think the
optimization is worth it on its own, and something like this could be
a further improvement on its own. But it is interesting to think
about.

James

#8Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#7)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, Apr 24, 2020 at 09:38:54AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 10:55 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

Well since it has to do preprocessing work (expanding the array and
then sorting it), then the number of rows processed matters, right?
For example, doing a linear search on 1000 items only once is going to
be cheaper than preprocessing the array and then doing a binary
search, but only a very large row count the binary search +
preprocessing might very well win out for only a 10 element array.

Hmmm, good point. Essentially the initialization (sorting of the array)
has some cost, and the question is how much extra per-tuple cost this
adds. It's probably not worth it for a single lookup, but for many
lookups it's probably OK. Let's see if I can do the math right:

N - number of lookups
K - number of array elements

Cost to sort the array is

O(K * log(K)) = C1 * K * log(K)

and the cost of a lookup is C2 * log(K), so with the extra cost amortized
for N lookups, the total "per lookup" cost is

C1 * K * log(K) / N + C2 * log(K) = log(K) * (C1 * K / N + C2)

We need to compare this to the O(K) cost of simple linear search, and
the question is at which point the linear search gets more expensive:

C3 * K = log(K) * (C1 * K / N + C2)

I think we can assume that C3 is somewhere in between 0.5 and 1, i.e. if
there's a matching item we find it half-way through on average, and if
there is not we have to walk the whole array. So let's say it's 1.

C1 and C2 are probably fairly low, I think - C1 is typically ~1.4 for
random pivot choice IIRC, and C2 is probably similar. With both values
being ~1.5 we get this:

K = log(K) * (1.5 * K/N + 1.5)

for a fixed K, we get this formula for N:

N = log(K) * 1.5 * K / (K - 1.5 * log(K))

and for a bunch of K values the results look like this:

K | N
-------|-------
1 | 0
10 | 5.27
100 | 7.42
1000 | 10.47
10000 | 13.83
100000 | 17.27

i.e. the binary search with 10k values starts winning over linear search
with just ~13 lookups.

(Assuming I haven't made some silly mistake in the math ...)

Obviously, this only accounts for cost of comparisons and neglects e.g.
the indirect costs for less predictable memory access patterns mentioned
by Andres in his response.

But I think it still shows the number of lookups needed for the binary
search to be a win is pretty low - at least for reasonable number of
values in the array. Maybe it's 20 and not 10, but I don't think that
changes much.

The other question is if we can get N at all and how reliable the value
is. We can probably get the number of rows, but that will ignore other
conditions that may eliminate the row before the binary search.

I'm not trying to argue for more work for myself here: I think the
optimization is worth it on its own, and something like this could be
a further improvement on its own. But it is interesting to think
about.

I don't know. Clearly, if the user sends a query with 10k values and
only does a single lookup, that won't win. And if we can reasonably and
reliably protect against that, I wouldn't mind doing that, although it
means a risk of not using the bin search in case of underestimates etc.

I don't have any hard data about this, but I think we can assume the
number of rows processed by the clause is (much) higher than the number
of keys in it. If you have a clause with 10k values, then you probably
expect it to be applied to many rows, far more than the "beak even"
point of about 10-20 rows ...

So I wouldn't worry about this too much.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#9Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#4)
6 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, Apr 23, 2020 at 04:55:51PM +0200, Tomas Vondra wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

I think we usually use something like 64 or so in other places, but
maybe I'm wrong. The current limit 9 seems a bit too low, but I may be
wrong. Let's not obsess about this too much, let's do some experiments
and pick a value based on that.

Another idea - would a bloom filter be useful here, as a second
optimization? That is, for large arrays build s small bloom filter,
allowing us to skip even the binary search.

That's an interesting idea. I actually haven't personally worked with
bloom filters, so didn't think about that.

Are you thinking that you'd also build the filter *and* presort the
array? Or try to get away with using only the bloom filter and not
expanding and sorting the array at all?

Yeah, something like that. My intuition is the bloom filter is useful
only above some number of items, and the number is higher than for the
binary search. So we'd end up with two thresholds, first one enabling
binary search, the second one enabling bloom filter.

Of course, the "unknown" variable here is how often we actually find the
value in the array. If 100% of the queries has a match, then the bloom
filter is a waste of time. If there are no matches, it can make a
significant difference.

I did experiment with this is a bit, both to get a bit more familiar
with this code and to see if the bloom filter might help. The short
answer is the bloom filter does not seem to help at all, so I wouldn't
bother about it too much.

Attacched is an updated patch series and, script I used to collect some
performance measurements, and a spreadsheet with results. The patch
series is broken into four parts:

0001 - the original patch with binary search
0002 - adds GUCs to enable bin search / tweak threshold
0003 - allows to use bloom filter + binary search
0004 - try using murmurhash

The test script runs a wide range of queries with different number
of lookups, keys in the array, match probability (i.e. fraction of
lookups that find a match) ranging from 1% to 100%. And of course, it
runs this with the binsearch/bloom either enabled or disabled (the
threshold was lowered to 1, so it's the on/off GUCs that determine
whether the binsearch/bloom is used).

The results are summarized in the spreadsheet, demonstrating how useless
the bloom filter is. There's not a single case where it would beat the
binary search. I believe this is because theaccess to bloom filter is
random (determined by the hash function) and we don't save much compared
to the log(K) lookups in the sorted array.

That makes sense, I think the bloom filters are meant to be used in
cases when the main data don't fit into memory - which is not the case
here. But I wonder how would this change for cases with more expensive
comparisons - this was using just integers, so maybe strings would
result in different behavior.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

saop-results.odsapplication/vnd.oasis.opendocument.spreadsheetDownload
PK���P�l9�..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK���P����[[Thumbnails/thumbnail.png�PNG


IHDR����H+fPLTE###...444;;;CCCLLLTTT[[[ccckkksss{{{���������������������������������������������������[�s��IDATx��}	����e���$;�}����U�R[����N�7��|��,�@-���G�/~}t��~$|���k��9n���C%�~��;���W����WK����?~�?}����{����D^�>U���~__�#�$'���LH�fr���r��Z�4�K�����Lp�4'�G�;���������a����t�����:�>�8/r��KN��&A����R�p�����%	J
���
gP��YA�$��ps����uI��fw�
Z�,S''�����������.s��$��%��?	'2����X$u{?�����3|'��3�{1����9���>w~��$:�Z�N��'�p���V�\
iw��7�X�P�W^����RA�J&xH��}>� �\
���g�@9�y������
��5��=�l�������G��U�,�$��{�p�����p�X�4������x%=������]j�M�B�o�W r�D���w�|�������N)Ye]�����A�K4��K9�c	��������
��po��~�����8K����	�4ga�t]�����O���������*8x��-��M��_���W���[��8��S���,c��"���
�����t%�����W	���o�h�V�X��4���mV�un�5��`��,�x�9�������bd�F�Ii�/�#O�9gO ^�����)���4���ex6IsKW�r�D��T�qP	�T;.�`�/E����O���i�gO�����ZC�K��XK���iCP]���*�Ki��<^S�U��40bP��/	W�k�0g��$]�T�D�|A��XH$Vz�o�r�a<os�UN��JO�����.�<�W����^���Z.�eb6��X{3�R�)��?�nY���a��G����hJ���m8���;��so�~O����,_�m���40��hk��u<=���`���w���@�������c����>[Y]Z]w���[�9�u�L�Q�R���I`����h��������Kw�P�����W\�
�j;%RVu��G='/0��C%�x�T���*H�L�o��~�s���G=w-���r��8_U�����E=��2�\-�i�d�sM�M�7R(t�l��"�e3�.��6��{�J<�>v����nT�'���_�m1i���=���c)�(^�>0n��9���D�
j�G^A��[3|���2���U�[��p�W^��!|�py�WM|���]���[���P���p�8��\Q8~;_w�J���Y���k	��$Q�����oN�I��;tV\0ntg���HMiQ0)����wl	�#�EO|9��i1��sAe�j�6�'�J�t�����Y��P�;�PT�D�L�/�N����4i� N��B	nF�
�\�`�/�sw���k��X��{�������y5��,��=�l�P����~^�`��3����D&�&B���L��e���c��poU��-��,�`��f��x�	���A
�.���cQu=��$���vId�iS������W��X���t�"����zj�V��~�������9{��-�+��U��7�����pC%�z�����6W��Y=����+0_<�c���XA�UQ�9���>��-���������p� � +(��k+V�;�W�)�P���hk(h�
h��0~�
�\)$�"D�\��$8������G^��`Bb��0�Wz7_�Z���-�m���I'x��PJ��3<���U*�x\����9���un~��
����{�{
��8�/e�u�`�Wz��������x��;2#��p#� p;T1y8*S*�u�"���@w����"�g���[i�vK���&���$W#A��YR�0D��TH��f� A��.)�{L����\q�`�O�'q+�4�a���%�rS���'��^�
tp)�!�/�+�kB�U�w�
0UcS� Sf�*[w��F(}�
U�(���XA����j�f�l7����`"�����3�;��=,�&(/V����R���8G�������Xv-=,n��&��nw������������_���-��l��.��.�.`i�8�9W�5�_����������"\�������!X<�e;���q�
8��d�q�6��c6���
L�����#EY��W��\V� �{}��}:3�c�Y�Y7`�#��hb��S��\���+;�1V`\/V����Z\'vW�[�Yz�'�~�$�5��L�,���]-y�%��isa�	���0��qp�(W����p0����������\1z���a�����z}7�xZ���mN;����/��x����{�[
��`��/��6ks]���I�;=P����Qe����WRjS�mJ�}�p~��z�f�6��|:��R��&b�,*
�-$�2/:�e�x�bK��i6g{��d�J�����*�W�W��:��NuKI|&�.�y+�=�:e�WW�_��\W~�U�Y*A�|
3�+x��@�[]�+x���
Db���a�v��z��)�sE�j�y����s@�%�����2��d�<p�^38�_'Z(������Ze�����KQ�L�@�E������l���[�����j{�Jx\���so�~�� ��C����~�u�c��SF�����9���fF�4*�cI���2H+d��eeX��P��BV����X�%�x�q|p���R�p�7?��N���xN���}������M��|qO�����U����~N_���u�K�a
$M~�2	s����,o��*P��+��(%��l����X����l��;WV�`�bb}��
x���V�J��H�t�X��,N�����i�I~��������XA������� V@x1x_��J���]_�kR�&��V8��FIW�k���L��?��so�����K�JX����o����l&������]p
s|��#�c/����K���K��(��)(��Y���i��8�D�b�c2����������:�X���	.UbR|�'�}d�X��V� ����sX�bO�/0��+p�����^��a�d:��X�@������
��XA}��f���.�>�[f����/�'U��MT��V;���H��T��|Y�>VP�B_n_Ps����������a5u]]�\ro1���Qr����9\O�Z�{��#o=�����3W^[}	��b�����fm�#l�������WPa������������^�fjf����VFT+u:+l��Y3�?���~9q�������@�/����KZ�]������{�U�_I�s��N�.��F�L�������BatAgu�������M����l��������[>C��b(�q���Y����^�V0���2������x�� 62�`�G��/� ���e ���x����d��=v��?��PbJ3��,k�����i���8^�6�8�)`�t���]�*����*x+��p�pC���c���W/S�b�e��m��)G��n]�xR�{���;��B0��Z���T�zh��sk�!����<������C9�k�laN{X��_�
��M��+?�����)_�lI;�i�
���k?���+��LV�W����^���@K�S��GX��0CJy~��������\xS�
th&��J<�+V���S�$:s�S��;Dc�a��G��^W@���+�z�U���e~��_`������&��n����t��y��xy
�	i<�;�R�d�|���p�N�����X�`m�R^�����`al���\?B�K�|��/$��6�������%�� ��4h�<�F�X��q�5�Z��#h��#���X�a��������b�-�2h_ ����q���y
3�+�u3��Q���@�9����}A��
�����%�[�aa4��sJ����v�
���^�+��.G��U83�)�|
3��R�B#U����"�x�#P���}�PlKK�{b��a�����]/���R���R[�c^Qj��VyNi�}�Sw�u�D����y�H�T�[�����v|�����_�
��u���_��8n��J�������W���0�W����sw|T���Co|k��te�?�-��i�W��|1�y�%�����Q;������#_A{��SuI:���6����a!8�z�?�
�=_�|����7C��|�Q�Q�]�%Ny����8n|�����6	�j�@�t�*+��Ql<1�
p�e��}|�7�&�����'�>�Ci�������c?�x��P�C��C�����|x����������>�]���w	WGV����������<6�o�
�r��x�Y���������y�0w2k��Z������6����mug.��o��q�z��p��@�Vs���WS�������5���9���N���+�+V���c�@K]{������~���E��N��`^�
0F0k��X����D��[~
�?ndn�u�F�v������ z�<�

�P��p���X!��N��I`6{������kX�|�E���Az�������f����*a2q�D��!���x���p�N�~)�e��|�pM�2����0�>]#�8�*�}�>
~����
��{��@��%�B���E�x
�X��&��M?�����
��[G
���^��y��/vle�qT�P2��q.���v�p#n)��'����fT=���7��.
,3!�f�.���D��j�SY6C�����K���4�y�
��4��b�D�H�$�&�p�mK�
Y;)�����0Ca�b�T�����Z��A��r!�z��B(��Q�p�H8Zbi+#������=�����pfv������x��;��-|8������
�O��A������p#���0�`�T��Sp��F���P��������s�����m�t)�����
6��\������j?�
���:^��U��ac$��������P}��g�c�J��KOpb�����G�Z��$#���o���
Ic��"�AL��#�N�� �>V�V���m���-@������M��� �0�h�KZr��U�u�q<�q�h���-a�-�vu
x�7��[��"�F�5�^�������B���m�aAn+��*�FX�#��=H��Jk|�W+:�O�QE#������(vL�9J��3�����G�P��ti�w��I������j�N`�����b�a|����d�V�[b*�'z��Yo8=�8oX���
���
��&����Y~_��}A�wX��`�%b�v�5���r��[��# ���7��f�ud��4�<����U�)$�W�������!z��\w�L���\X���4����0���:�z�-��.�?�������
�
�����}vQ5�69j_��}�]���G��#/��W�z��WtB)ex3�k���/`��`_�`����/��/P��@|��}��&�/�o��`�m��OO�=���}�~��_��^7%��/@�����p��:o)�O�j��~Y����B����_��f�+������w��0���8�/X����$�fH���
��b��RS�)��I��8�*�Kad.�u6�]�9��}���`��`���n_���H���+�k�{�S�
��b�C���	<����9�+��7�|��`D�.���Gnc���7��6�`�|D/�.&bx"�����w�S��W`�u���r�XA��T7���'���c�W�v����^����
��|�E��_�V��~�k�����^2M���+)����F��+���bi4|"���W�+��w��	PV��l�d-�9�:Fnc)n#b#e������V6�F����+x�o���������?9���t�N�y��W0
������_�+��������%20�9����-��H��h�������Lw�z��T�:����M��&5'�)"��Y�Y
ma#�����������X�3`�Z{��?"����;���V��V��+��Ac�Q��t�Do9���K��ar
3���O2�����u���P�0�?�f�����y��"��Wg������:�3+GT�����R��|HE0�:��;�uV��[���w
~/�������X�rfp�|n�9����@����A����S6�n)�"En|+�1n[����Onc�4����8o����@�]�����������Nw��1oJ���)n������MO��
6nc���o�+�~_���-+[�u�
���/�'���l������u���p�
+_�����5{����AvN������Z���;������;�M�����:�+�o���p�]��A�M\��.l�����-�w�l]��5
��?:���|��^�+��Hv-��'�9f�1xui�`rvs��H4����,�((k0�2�\��1���g=~F��[(�����yr����x.����mLr����GXg������$V�L��Z���B���'����b,=�����~Y�����(?��v��?����l����1�c��6�.�����<w/����'����k�91�qP0�����X�c�Cnc!J�!�.#!�o�J�/G�JU��X�r�f����u�Vo������K�<
:�p~�/����u�y& �#�7�����(f�ua�2�A����Y��	�Y;�+X�����E����V`��<���M$�|1(;
�7<�VW��=���3x��[i�bh_l'N
<q����`3�����D���KH�7��s�i<�+�FwFS�y>$n�lcGu�p/���E'W��-�R�e�E�!{l_ N,s.�wC��a��L��5w`�{\��XJ0Gw~�F��_��
��[�����P���8��_�`����e�;%�uT\�QS���`��R����Q���W��t�s����_���W��������<Qb�1��	�D!�z����e���ph��
����c�T�h$hW���������V�+�
z�W%n�9����k]�!Vp+�,������>3�X����
��+_Y�u��u!�����E��w�@%��.\
�bI��0R_��0�z�F�(��|;����5(�����(�
0�y�s�v����;��y�G�z�-��)\,o������F�-=�AT�g����+0&�*�<*,�>D�O���Rqx!��8[�� ra�d8���*w��e�-�R�M�VF��#����6i>��
/�U(��#�x?7��QK��6=�X�=�<y�v;�����{�
��n�<�#V�������
��&tp����iy(��[��fX[������6#z�_QD����V`cY��Aq��4�r����M��yC����3����{W0�H'B[(lj�xhY������2���~g��/oU���)��g���^.Z�g��/�wa{n}�[��%���%sx\����F��Ro�lGz{��(�p��A��5[���a�9�����g��63!;�LcB��3d��gJ"�a8}MIt��0�3)�ja[8}+X:���;i���J�0C^�n�
�(b?Q�a���$�pv��^4��i���S��]&��R����>���G|��D�M���#L�
g��P`��L�8��vW:��PG�e���D���l�����[��p���1��v�yIEND�B`�PK���Psettings.xml�Z�s�6~�_����#	%L��p���0@������&��#����l�M��S�Xio�O�e}��^��+����e�-I����Y�����|������a��5�V������Wu	J�[dMO���-_�Ie�dO�=����������+;F�S��(��� ���3�F�����o����:/U|�s*D�NN�������E#�m�F>sM��p����A�Q�
��7�������){[
�w�Yi�^�y��.�z�qP�==H������ o��J�A��:j����v�'����7���/����V^��K�:���I�A����9:��>���9	C�:�Ao�4M�� �'�f��)����#_HS�T��W��/�=����!�C����+����t ��
�b���)���G��F��oh+��}F�:�=3������v��TN�-7QoP)tK���.4J��>�'Qc1h�YC�Z_E��o0�M�d�^"2 �(�C���m�Lp�S���X1&�&�>�kE�� K���2e��$��H��<�IU(���PN^�T�R�J*U�T�U�]��}9�pU	B%� T�P	��L�������8�����:������������n'�fZ�Y� OS��d���C�F����O���)h3k>D!C�.��y�}�)��p��Hm���c 3$�
����������{w�L�!)���D?��K�?�0sf��	��N�9�8#RA2�� ����Oe�1�@����,Z�_f�$|j{q*��_:tKe��%��_4tb�����^�"9���?w�s8�K�A���_�	��-G���%���zv������5��4
%%�<�D��,���x8��o�0�E'H���4��HH�r���~�����H�L���.����n+�Y<xQp+t!
:��w��3������I��D�p"����� �[z`^?�d���+�|�P� 1X�F1�����[6r�0S+��j��������3�'�Wi8��)����������6Z�}�#�l���������v��H�\�P�;�H������DX��^F��yQ(>E�],6������2���/�6�^�PK���gl,PK���Pmanifest.rdf���n�0��<�e��@/r(��j��5�X/������VQ�������F3�����a�����T4c)%�Hh��+:�.���:���+��j���*�wn*9_��-7l���(x��<O�"��8qH���	�Bi��|9��	fWQt���y� =��:���
a�R��� ��@�	L��t��NK�3��Q9�����`����<`�+�������^����\��|�hz�czu����#�`�2�O��;y���.�����vDl@��g�����UG�PK��h��PK���PConfigurations2/floater/PK���PConfigurations2/menubar/PK���PConfigurations2/accelerator/PK���PConfigurations2/toolpanel/PK���PConfigurations2/images/Bitmaps/PK���PConfigurations2/popupmenu/PK���PConfigurations2/statusbar/PK���PConfigurations2/toolbar/PK���PConfigurations2/progressbar/PK���Pmeta.xml���o� ���+,����]� ��z���&-�z���l"�u����q�N9���~��{��c��wp^Y��"�QFX���A?7��
��_j���0iE��	i�'c��l.5�w�Y��g�w�Y��,-��f�hV�Z��
z��0�!���=.��5�����z�#%
���EV����o����d�=M�:��<��|^���R_`dK<&����
�����X8A���)F[K��hIN�4�RRm�5+)+���9��%���M�p��u��v�'��H�#��8!H�wMe�x������Z�~��z�s��#4���C�����-�����8��V�}R�?n_VtK�����"��}���)9�|�7���>�}P"�z�;
���	
*�,
�z�hI*z��n2[*9�m�?=�����PK�O���,PK���P
styles.xml�Zmo�6��_a����%�i{���m�mP����`$J&J�E�I���(So���I7;H"����s/"e����t��� ,;s&c��,d��3����{��/~8eqLB<�X�Jq&�B�R\�@8+�z��Y�l�PA�y�R\�E8g9����F��RzD)*�����7b����d�������8��X����P����1sC��H��7�d_������6��x33�x��l��������+N*
=L�\��&��g�)h�}k����k�S�jE5���+�r�"[��_�dpv����%���L���2����4�eS$�=�=��������6�x:t-��Qr�vS�my�Xe�����
|����z���D`n������b��]�n���k�������_z�����xx�v��T�"���*g
4�Q�	s��-��&x����v�?���_;&�'1U	��f���v�U���d��s"�Ub��;c(�Cey�4�+��T[�R����pII���%]������P\}���+�*�7�u��i�,��3fp��Q����X���W
���4��yG�}*�G�PI
m�@SBo��Q��_8=��j�%�Mp^C�R5DND�e�8Q���6�
g$R����5�P��v���y[�>����k~b��>��^_��q��2�D8F+Zn����VU�n�)u<G%�K7���\���)@���)���lz��-m2i�b���$����,Y�fq�B(S�������
�#��B��F��~[��R��x}�V/�����d}�
�$�E���.U�:��8���roS�����w�Qy��L���"�Q@sPu�4�i/�F������/�'FrO�CxT�T��w������`���W/�*P�
�#������g��@t;��$YB��f4��p)�__����)�St����^����}rr���}�v{<�����k��;���	��neT�W%�`M6���=�k����(��e����\r������jNje<��.�
*nT0
}���/����
�T�ZA���D�8d��UR0�/C>Bi����}@�d�a|��Y:!����`T��@6$��P���g��~����8T���X�����}j<U�)E�?>����,�K�|�"OEJ����2�=9)��'�I�@B����{HB�A<�w'�$��r�-��?}GP~�u�^��~{���<}�#����qb��=%i��B����}�cK[�Q��m���h����Yi���m���E�5��d��Wr��k�3���R������������������V]���w[U����H����Ql v?�LT�~��dn�V\>����Q�q���,�,S�����G�P[�p���v�{�G���<��H��5f��@[����t�t����O�`'��,9�]q�ud�Q�|&X��pd�?u�C�e����88.{Z�xS�0����7F�����Wc��<u�T��f
�w�X��kEa@���S���B��h�},d��r�/|���#��W���b�B��j%J���@&}�|���^o�*'RTT*�VJM�>j�����i����5y��Xb������S�9X��
��Q���Z_sS��A�R^H��g+��Y�k�`T�H�i����.j?��$��l�b����tf��l=�-~��	"�
��?���XR���"��aJ~=��\����k����������Y���~|��B���������OetW��{��2�{����q1��z�@��uW���}���PKq���(PK���PMETA-INF/manifest.xml�S��� ��)��%���"M{(�t��c*�Qt,���	M�e)4��:��7���{W� &��a��U�����}��O���6�Bk ��U����mX�(�J6IT=$I��P�6��$�������	X�+��p�pc'' �3jE��J��%��7�� g#�vU�-��.��r`�suPtl�x��h�j�h�
��v$N����[�)DP:��%R�����.	��<`�@��Ub�/bI@T��xI�2� �P^<���������{��v9�i-��R�8����v��� �zyj�.^?�b�����F����_PK�u��1,PK���Pcontent.xml��K��H����>EZ�I;�p�=UU��X���d��L�1�Ek�A��U�^��e6�|/<�q�_�U�!|��~�����W?�������7�14��?��y~����_��������d���������?�|~��������y�����7����C��o��t������7?�>{���Oo���y����?���y��O��e�O���|�������V���~����^k�g��U��[��������?�����?�����������o�������/�p�x������������O����������iX����_?]��Ouo�{�U�x��������=
Mx������g����_^��__���N�h���������w�$�s�S����3�{�����t���{>��{v'?g[�������t/��������=�w~�o�?n������su�Z�^k��>��w/����K����������2����m�?���/��f�ow/���}Q�������z���}��-���������G�� Z?�w�������?�{q�_���������y��s���?y����go>2w�M��_:<��y{{������o�[�������W����������x���t9���H�O�������;[��<,O��/=��������L��3�w�|������o^\�|7�x{s�r�O�^m����
_>��nw��~�}�7���7�>��O����5��O^�KCz�o����������������A�}����?����u��vo���z���'�������w�".���1\,x���~N������<yq�������b��/�p���u���}�~`l�����4��K_�|��?���={{���C���?����z������A$.�������U�}��y����^nO��o_���}�b�n?����?{��������V+���w�o^t�������>{��U~�=���w�?|����o������l��c����=�
�������H?�_��������_|�6=�7w�_��������zw��?���&=��/\�����C�o/_�?]����������r�um���m�9<������~l��a��?���8;>���G{��fz��6>��������X���}�X���b3<���
i��][h�����75<�O�%4�Gjx�
K���zw�������pa�+_^��?�_����_���b���K���_��<�}����g��|�����z�_��g_���_|�,E��'o��r��C�����������?���y����W����z>�m���/��������7��{�����R/M���_�|���������9�Ty�~���R(�>�������<�3s�����7O��?�����i����}������
��}��kT������_��O����������v��������������O��|�����3������������K���}�[������H��������������_|�������?��}���i����������?����=V�7�
�b?��������e�������Y�o�K�y|�/�>��=���/�>��y�����??����������>M��������=tl�����O��G�C?��~}���}�s����������3��[\���{�/x�����=�����_A���=T�{)��qi?�L?������(����0�?��O��Z�����y�3qTh�������G�sN>���jL�:0���g�>�*��B����L}���?����
��>������Qq��F�C~�v����u���!����������{����|���(������-6��Y���Q���G��b���y>�c�n��96��N�~�|l�-�7�Vn3������q�:���i�t�[�W��>�ksNwT������Y�������0������������Q?�}���<�{?�>�<����n������x�����������w_l�����y{��������������w���_#�z#�#�#�#�#�#�#�#�#�#�#�F#�F#�F#�F#�F#�F#�F#�F#�F#�F#�&#�&#�&#�&#�&#�&#�&#�&#�&#�&#�f#�f#�f#�f#�f#�f#�f#�f#�f#�f#�#�#�#�#�#�#�#�#�#�#�BkVh��
�Y�52+�Fh��H���Z#�BkWh����3���J���������"��ol%����|c+�v��=�[��s����J��k/��Vr�\[x�7��k�����\;�p��������ol%��2|c+�v.�8�[��s����J.�i��j|���f�����j.|���f�������{��������|�����g/������_��]�vr���������y�n�qK����/��x���_o^����??{����5���w7�������?����#��V���W/�?�{����-�/�q��
/��w�n����]�����W|�o_7�����p��WJ��w>�����J��������7/�M�//.��e���n�S����;�g���2��c��s��w������tw��W���8�?|���}:hm]����?��c��]"��>�X������������7�n������������������>�w�����U�G���.�{�o���������������}���q}d��=���>Bo�����q���^�J�5D~��T}���(_����������������.���m�Q?��!���Ih��������U��?{��s�t�������!w��!��O�mZ�A\3=�4���c3�����*��������K��o�����G�~]�'��*��2�DN�r@��^��� ^�Z��!'r���511v�����U�bW�
�Z��Q�;3>�@����uu������0
��2j�$����V������2(�e���
�{a�.`��?�@��E����G�}��E}K�*���9�I���Q�;�>�@��G 7n{�0
�~{4nKV��<�����-(��_�i��6����j�U�e����|��[�#����$7n'����E���p�
G����5����Dl�����h���#I����z��r;�e���E��mi����m�.`��?�@��E����G�}�[���U�gs�c��Q���Gk �7�k�7&���1!��hQ�;���n�}(��l�l����s�m[�����-'o��G(���n�
D�lm�Nn�v�m�9�M(�~2	�V�h�8�} ka���r��m�
o�J��Fa�XF����m��r��KK��G��P�v��~�P&��f�x��V>e�=�)�0
�d�sf[k���%�����o(�m]3j������-�J��Fa�XF��$��@y���l��G��P��E�w�@�l��Q���,g��l�0J��?�Z�<��mS��Qv6�����r	,�n���@:�.�J��H��`H��$��`Y�n��c�CY���{P&�~�QuUn��`:
�I<vw��?*`Ff�I'���(���d���;��������2��1����A���(��mO�m�QV��Z���e�G*���2�v�/\����+U�0
�3j�$���Q�q�*�����,�
���"P&�����S���*��
����G�l��Yn8�4��,�f�98E�L�uu;�"��0
�0J��?�Z��n6��?�����:E�����6�)�ez&l-���+U�0
�3j���d{r�u%Y��?�N���,�H�"P&����EC�vmS��Q�0Z0��H�m=�<���R��1v:�����:E�L��{���V�,U�0
�3j�$�������*��������8R����{/�x�����^+��
����G�l��N�k��1v6����E��)e�����F�l��maF���g[k��-��w������q:&�'�������p����T��K��EQ�^E��t�l�n���&�JL�u�0�E��l?�C���1��]L�dL~�n�6�Qm��J�u������H��h�a�tAnl��;�V��"FuX�(���*jo$�����YZuW�T�����^M�i[���W�@O�����\n��h��i����R%�:cTk��m���7���4�(n��U���Y3��h���I��ar�%���1���L��
����9�Z���x��4lK&����
�F�1���x�����7��n��V;�j-���b�����*D���C�d:���]IF� r_�m�����\n��`�����l#����b���X0���z�����K/�zEQu� �e[����%�:+h��dL�d�&��-�������V������v�������-���/o�����_�
�50�/�j�Z��������}'�M�v�P�&E����lOo�zJ���p]L�m��l��E+�L��������<�k`R��(���dkl�(.��*1������x�SL&�zV�x��S�m�DQEQ�m����d�
%�,nC|�4L&�zV�x!�,2�
�\�&b(��k�������T���L�����^&L&�zV�x����kRe��#���QE�U��H����!�JLvf������=&�l����7�b��
��R������Q��(Z���F:I�'7y�Z�[��1v:��)���s�1�d�WQ��
���TH�t0C��u�Z��c���a�4�w�,N��p�D&�����P;d����BP%����cy�!����������7� 2�v��P����[%}�PRm���X�nQg��JDv&�&C�7%�L�uu;��[��z��'��J�E�����gH���Ldm�_�I9%��S�6�*�ez&l�&��:�+U��u�0�&C��t�j�.�:!U"����i=��SrJ�T�������p��3��J��;�Z���k�U"�3���G!2�����k���6�Zo�A1C�5�^HR���^9U"�3��>���#2���������;b��3��J��;�Z���b��}���37��I��n�:���Pu:hI��yj�6U"�3����[���<��U���u�e����L�V�����io��<{z�[����84F�Cy$����Y�
�T��^n����9;��\��Y���@��8qa����@�
����Kl�\Kl�?������Y�Y�8U�1��?�i�V�q�MS%�1��[ �fk��*�L�)��>���S���>�������A���2����AQu`$�����z�1�z0L�dLv�$9����"��]r�������4kf4�G��\G���nF������
Fk`��0j�
b���*y������5@������*������4j+��m�l�*������q6�a!X+�
��@�7W�W@�
���y� 7g?��a;��li���������h��5�9[����9+ov�k����5�|����;������5H���Qt��k~@�P�Y3=��04���T��^n~�����^���5�;u�A�Dc/7��v\���o
�I����>Wa�r�V��`5-���#��>L��o�L�,`�n������w]�d��gE����#k�\G�����QE�U��H����`;d�D���io=��_y"2������k;�$�.�I�tPE�����c�9���}�<h��p�L�L�������A>Hl�u�s����~��H�u�q����\���x�S�}���o����o��.���4�z]��,�7]�n��$���N���=Z�@�T���n���&2\�k"�i�j�.A��
@N�$���e���u�[%��Xk	0��I�~�4^��
B���;�m����XG�%Y����w��M�>�,,&����x�����i[��a(��g+�����zy/�>Wg��� ��5%�L�uu;���l��'���I��5�Z[<�3�f�|��X�	z�v�XL�����m�������'Nm�t|�>��P{!�����,��iw<���6�y���<&����t���z��H��`���9�*�����>'��`1Iv�X�=h�$��d�=I��&Yk���7W��b7k����lJ��$���W�N���1K=��$[m���8�SR%{�x�>��lJ��$��v�zuC��n0�����js����x��V��n,���hrr
�&��������f:��V�-?7>+��G'I�e����m�b(>�Fr���M���nS%{�q`\��/�l+e�� S�������������M�z���z�No�	�O�t�	�)[	�����g�-��C�i�V�q�U����l,f��e�$[%~��S[n2��)�����7~o���|v�	����@c��{�T�>���`�`Lc�[�����l,f�5f�l�:�S��m�~7����z���n�]�.`��O��Ow���`�|�WdK.0c�����S1N�`�`\`c�0Kc?��S����*�t�g���z{�^n��4f�>��h��v{�����f�~c0cw��-�����
�Z�T	�`�6����(o1;�/�?�_A�����
��?�d-��TX���b��������gE|���%����I����8�7�~�c��c>����d�tB'9��k
q���T	�n0����?�����4�&v�&Z�>�>��`��nV_��J0v��v@�x��@`L�u�����^>��p��O�l�Y���S�ML�`��I����Mc��c>��t�\��,��I��7�c'�%��K2?k�}����1Y�~>�U���	������:��6���N{���sC�M�������7i����'7C1)�-����Y���<�<��P��������n����v@���n��>�{��,4�<����X��^�)���bmZ��!�@L��{����'�V	��	��X(�Aq�nl��b7k&L�)S��XW����Y>q&��'x�ck���k���R�P��b��MNS�h-8�js�R]�g�viA����~����iO��{n��Vko�P����k�X���X�xZ/-�D�J�O�$�Bq������X[&=q0M�c�^���^��[��'x�ck�����a�kgWLIS����`���W_e�J�O�$�Bq�n�z�W��b/���6sM�c]���X6,O��6Z��'�8�-�HK��t�Y�A0�G��A�
N�e[u�l[�12�F�������R(-�R{(�$����h=����8f`>5�HIb��y�� �*�h�30
��9���\��Q���*���Y�}���<F,�hi���t$�f�e�[����PZ4��P�q������l���0��a��[���0y1o������L#�xK�^�4U���T[�2�H��-�7��n1���'~u��R(��/��[
��l���0��a��[��&5�����
��������4rK���UKS���q:K�
1��w���J,�7��������=�\(�R�����,�y���
0�o�i�V�:�V�����2,����qK���7������������n����-�7��n2�!��w#�B)�������N�����8f`>5��q��������`�|�Fn��������>��t�,�Ks�R���0�-��H�~A+�Q�U�P
�ESj���{z��Fn��?�N����|����L�uN��f�mT��I��G�Bi���CI�u0������2>,�gYk��w�`���R�]�^=�,U�P
�ESj%	�	���
-�*8�Y��h�~�"`&�:��x��V��k��q�@Z4��L�p}���.w���E������I�����R(�R?��CI�usX��nR��qv:���w��f2��)UW��b�rQCh�Y��6U�@
�ECj����{n���M�*������}���o��[������ZUGX��0������=��[�Q��]�MR�J���t,kS�NB/�e���0>]l����G��h���#I���r'�n;��X���-Y����`��!���ik��J��H��hH��$�z`Y�e��?�N��6u{���BX&����Cs���0��Q{$I�P^Z�T�T����emf��J`�t�mH����2��%c���T�\)�
�=�N���Y����U�e�cY���p�N!,�o�����V�\)�
�=��[�Q�RkP7�I��G��X���,�S����`��a�<�����R -R{&����W��-iY8v����{/�x� /cX��@J�=E�5fy*����(;�A�����I�{/�x)���^�
P
�d�sd\k�YQ6���������q�gq-�tKl�38�K|P���HZ���N:I�gwy�j7U�2.��g�������0�y9U�2.�r��"n.J�Q]j�*�����k-N��R���JM�bl�I[U�U���Q#�"i���;I�u0�����r���n.�2M�
]M��/�R%.;�}�\���/��-p,6�����J��g�������:(�7��L3���9�2�E�����I����I��'5��J\�e\��2M�J\��
8U�2.�r��"n�&��)n�kS\(�?����^���J�
���/�&tZ(�*y��H��Y@�
�C�L�^���q�=�L���(.B^+q�q���7[W\l�UB�7J��O��;��:(�7���/�vP���I�I=7h�V�rh�Q���*q�q���4q+q9���T�����ew�������'��	(�?������w���:(�J5���L�D�#�$�=q�J$ER$��,���{r��Q�Nad�0w.���]�e�oI��^���P�J$uv���2I��$�z����GD�J\vv������I��%���f����2M,����!)��-���$\.�A]��*q��������E�L�-v���%
�2�Ep:�")	��t9����J\v����#���$����xE�8�o�R%o��II�gH��.���\����~����w\&�~SRu�n�%�����K��z%����8J�E��*����M���MeqaK��P�t��Q�����{��=������9j�$��~X7c���*Q�����D����P�t����1���L�N���pG�v�^I�������|��������e�L��{�kq3���.��(���tk�rT�m�Deo*��J����2�v�"��������qGI�gH��*�G�o���Meq�v���2T.8�j#!�e�!���E���J�8����GH��V�����R%*{SY�����6Q�t��Q����0i��Qg�G+s�^I����n����H������W��i;e�L��{���u�;�T��"�{Gq�t���U��b����,�jr�2mT&�zv�x�B?����L������8J�E��*��!zVC�S9�6DN�)Ce���0^��/�ie�GqGI���[k����z�*���8k������"�N�����%�Z����`������<,pG+v�^I'���*�;5��>\�e\�%i��������*q�q���7����pS%�z�T\�-��R���J���]��Re�O@I��nI��$�z��0���R%.;�}���4qkt�]��C��eg��+s����������R%�:�}��s;�uPZo(���f��Z\2I��0$ER/���p}�,'�>W��e\�e7P��=������k%.�2.��y �fk�N�<�T	��(W�����@i��J�7]�W��hGq�u��n%*�8f���1�� IR�`r��5k��q���9o�5:y������R�5f��%&��Ai���xW��S���2�M���bh���I�u!r\�S�R%"#2"���m%"����[+���>�6[�6����L;���D�N{����u �/�j����n�vG��������H��APO���.�� X��e��j��G*.�p}Kj��b?�sdS%�:�}HZ���N�p=�f��I	�����(nv����I��%5�yfR��k%��0C��ug[����yBk%"�Y��8^�G1"�j}j� �����H�Q��)������.\H�����N��{\p�|�����^P%M���BR$%��!��<��
l��eq��;�2	�����ss���]����n;���(��o%��[eeL�g�x���/�����^S����/�z��Q%���g����k%*;�}����0�2T&�����}��d�*��(��n�O��*r�a`^�;���~g=��2�v��`�����]�A��u�Z�u��1[%��X����(�2<&����sPwRL��~�����3��u;j�$�����$�E�2�EC��*�����2����j#!�e�!�+��d!�fGq�t�(���*�=���
�T_�v��S����2b������L�kpGI���I�^M������Nl�w��S�����`�fA=��9��+z(��d[L���Q�o�*Q�����:���d[���[�{���TI�Gq�t[��VY>��u:*��}�hC��:e�L��{�����nSe�[��\=���>�kx�����3��8�Xl��&���������Z����.Y[u�m��P�Pm��vOr��O�J@d@v	�B��]���/��L/��O��9���\�v���M���Pq����PH�gh�1�x�m7�sR%���qZ��@�i=��f��7g3o���8@d���*�����8@���h�8�nT��J�����[��1�Vah�1�x+�qP�@H�
���/@������R% 2 ��Fm% �j�o�d@�B���l�vV��C�*�.�_���Vah�1�xW�.�3j3j!B}�h�Vr\��R% 2 ��Fm% �^9U2 r�MBm�.���*1����m;��Vah�1�x��n��G�)�z������)�#�JX�eXv�2M�JXn�Cq�\���2,�2���%��x�I�I*�v�_��	�V!i�H�A�f�%�����d���B��A=����<un�d? ������:�dZ����8���V�9�qZ��@�i=��fP#�@��s�z��h��P8&���zK�N�f��h!BI�u'Zc�{y�X��1O �[���o��dZ��Z���L;�i!B��ugZ�=�fy���L��Atax\�L���Z/K��Nke���� RL�l}��vx��,����I��bT]s���0^)6�+�&V�(��j���ss'uRm��c?������pL�u������w��x����h]���H��������T	�nn� �<>p�x�g�^���I��;U(�(y��<k�B,��J8���8�o���"8&�����r6��P%�V�g����K�F6���St���"8&�����P==��<�gp�<���8����q�yV�.�
�]��E�!@�<�5����^�G�V����Co8������>�F��u@0�O��O�,��f9������~���pL�u���� /�
,�P%�V�g�W�u��T	�~8�8�����{/�z5�"6��JP%�V�g�W�y����\?��
#G��1yv�X/?�t�T	�
���������Q���J8v�X������6�,tr��l|��-8�������#ib3N�#�V�`
��cjO��t{v�q�����8�fh>9�XIj��ym(��(����A34C��� ��4�:u�T�h�OS�@M+���hj��;6c�MR�*��0��1������f������8�fh>9�4t��94}�� R��q��\8��/��-Q��W_��J�C�|��?G��������FS��H�L����T�s���,�9�[��q��|r�i�VAsh�����*��3h���i�|��n����Fu��T�h�OS��b4�D�z���>��Y��[����`Z8��T�s]��N\�L\�fh.�f�U��(o��r����A7[CW|&�J�C�|�����n<�V�i���t#�����[����`Z8��T�s}�,��;��.4C�{�i�VAshb���U�g����|�
�5�U�T�h�OS�h����V�i�h�}"ka��B{&ru
��]�V�d�)���=�^r��iA�9U�g��yi������W���d��S��I��G��i���SI�uAs��2w��Qv>��f�x}+�I��)5��1tr���e���iu��SI�u0���sS��qv>�������������]����X�u0Sr�ir�5�A]��*�����<���t�O�L�u������MS����`Z8��T�s]�guBY��?��G�8���#�����mL������.th�6�s��?.�J��J')��0�f���[��Qv>���7]������R�E+��!������R(-�R{(���03/�x����'NO/f2n�#16���VH����Bi���CI�5��k3����S{���0i?WS0�3w��.o���B�/J��$����0�}��>�C��3v&��)f2��0^����+����@J�=G�5>�lQG�Z���f�����z
�����u1S]���va�$�&�
��
 ��!�g�I�=3��i�8x��?����c�����L�uM��z�Y<	b��?.�J��J2��'u�����(;��=G�3�����}���F ���G���L�p��u3�{����G��`��8���I�{/�x����7U�P
��Sj%���,� ��B��Y��f�)f2��0^�0��������Bi���CI�u�<�0�J���|0�Mf�cy\>S�m���m�'��{��G���L?M�80�B$E��%�w�I�=���YF�[��e\�e�.%y���q�"X+q�q���7[�m���q�Q*���-���R���J���l�P�U���~�Z��HZ���N�p}�,&���q�=�L�
�C�u��T�����ew�����Q�-e��Ro��/3�Ai��J�w�Q_N���$UF��$�[R{'I�v3k;:�������2*���Q�(�ss�s������pK�t�j�!UB�����zr�o'PZ���R���A�`<Uf�zU�I��jI��$�:p96!h�m�������e��U����c��q���<q��zyn��(��������/���R���c���{�(��n���D�V��J\�e\��2-�*\Ml�y�-�G�2.�w �fk<��R��������o��uP�/�j��Z������l/���f7U���Q#+��nI����pO����'>�J\�����9`�5.�p}Kj���8���R%�$ER�����x��V���\��#�I��%��vQ�w1�����J:")�V-���$\��:u��T���n��M1Iu�\��$�b��N��[X��W��HJ�=C�5�-����fK�����>-����$\��/Sgr6L�d���(����w����r����]�������2�����vs-(0^���-��GqG����oO�r��'�L���U_�.��Ee��gGMW2���w�i��F�</�Q�-*J��������*�WY<�|���2T&��9�1����<���q93��h���+I��������t�1C��*����,Ce��nElW���Sk%��(��n�O���};uG�T���T��Y8*��I�{/�x��8���!W����qGI����w+�����,�H=�XT&�~�Qm�U���T��_FuW�TI�Gq�t����*�������,�]8o��I��o����^�w���w���:�/�h���+I�u�������d*�WY�4�p�N*�n����������>��Q{%I����fuG�T���n�"n��p�N*�n�^����^v���h����q�nG��$�zPyRw!O���Ke�����V��^T&�������:�*qGq�t[��V�~f*�WY<H��8c'������,�������J�*3��`(�Vl���N���E������}�|�dd"���pH�����\j��X���h�(3�H�=���[�##A�#��Q���,7k�|����^8��;j�$������6lS%*�2*�U��m%*�C{�DeTF��{��l���m��U�/H�o���H+�������"���L�*���q�nG��$�:����}�\�$�2*��&���;4��X+Q���C��T�|��m�c1�����V����j[��: ����v����T��&�zCOo���$["�;������IB��q�������K�1i�5k{��X+A���mk7[��_D����;���lf��(�zn���Fe����Q���L���cTUN�����\r��x��eV!M�@�Rm��T�@�9�j��Z������lm�Y�D�*3�d��Q��{I�'WY���������T��E�L����hw$��NnQI��jI��$��py�t�*q��������Mw\&����x��Q�t�Qg7Gks�^I�����n~��M�*q�����xdr{�J\&����z��I���*�\s�pG����I�A�mz�DeW*�s����[��Z/����rS%}$ER��������V����;��y����P��|�.�����x����TI�EQ�l�=����Q>Qg�u�&�5Y����a��$[���_��e�[%��(��lkO��&����}�,�|�a��L���(+x���8J�E�O*�1�C�sQy���l�T��E�L��{��f��W��Q�Q�m���|��<Olb�;�E���I��5^����^	={%�(��m����&�r�a��q���&�g���)������r���T�$r�
$[EQ�m�d{r���-k{��,��8W�����o����n�7��r�~�a1�(�V����$[*/�l�T���TO�9�Q���[����fQ�#���\�<�LQ{#����z�#��$�JL�t�b��	��S��$��`�b��g�v�f�����QE�V��H������}���t"'��a2�v��W�3�\g6DuX���
����W�l�@�>���R%*{SY<K'����'
��������n/`��t�;�Z/��@:������U2 �K�����vQ[
���m2j3=Mh��[%��1T\xM���P��C�OsT_xez��zz@�y$���x�6m�d@d� �����N�
�.����_�@��<Ac�]��J�c���P49�C�Zo5�L���dR%�z�qZ��@�i����v��Z�^n�1M�9�[��[3?7�+����������k%���qQ�����UZo5������s�7]���0��wur�����������d������g1��3H�dH�a[���i[���G}9*�G�X8���z����������>\
�>�4l+y���q:��2 ��vm% ��*���&�6[�����*1���b���7�Z���b�������������L�90B!�i_��F��A��Q�*�����x�	��L�uM����q����$�J��;��,�i�B8����\���48&���zgD1m�y.�U	0
��ZP��r�n��*A����Q��=��r	(�m]q��W�����B(���\k�������*�
�����:@.d2m�B��{�o�
�J��:�Z�G��b%r,�q��pL����J�\O����Q��52�@�<�7����n�'�.p��cq�;��1y�1����i;��P%�V�g�9��i�����9����1i��q�A���U����j�#i�|T�-ug�T	�^n\�����1y�1���=i#{�(��g������q��c?�*t�TS����`��`���O��O�l�i�zi�(O������7��q�iV����2=��F�����-P�l�<{n�G������<q,v�q�M�gw���`��0#��O�$���}����-':�X<�������4�P���h��"8�DA�L�w��I^ �����x�M�!7E�L��M��"���*i(��i+���Ktgu�T	�n8��nz�)�c���0^����E� 
�dZH���/Z��*!��/$���M������
�~���m���mk<�&6S76]+��0��1���I�=;�����*��3h����|���f{�C���)���8�fh.���r� ���n��J�����8�����MNzDS���MMW��fj���k����`Z8��T�s}�,..�*��3h����LC�
�C�Eyg��ik1h�fh���/_��[�������Z�h�OS���v%B�J4�7��.`j�Y�`��?2�L���Jr��g��;��f`�3��*`^�����T��A34N���s��46�f���]�����
���@�J4�7�������������S0-S{*��Fv3.�:�T��A34��f�U��� ������8�fh.���h�8�N���1s��
�������\MkZo45��<6���K��G��i���SI�uA���<30�w�i�Vsh�V�n!U�g����|����4�M��sG�-8�T�-���G!�V�i�h�a�fz.�g"�A�>70�J��{���f��a^�M�R��qv>�����?W����S��$c:�)��)9�,9���nRg-�J���|4������A39�9��[�����z��?2�L���Jr���9����X^D�X.�e2n�c�^.���+L��$�z�M�j�!U�g��Y�!>2�L�5��x�C7��H���G��i���SI�uAsXT�S��qv>������������������x�C+]�U�P
��Sj���{r�guep��?���8�d�~�	0�q]Sj�����;�q�J������������J���|0��z��^�d��`��!��+����PJ�=G�5�����k��Qv>����t������R��
��.oH����Bi���CI�������9�
0��su�|�B`&���!�K�Q��52�J���{��k
s'��Y+�����,�Z4pTO!0�q�_��l�Z.m�,��*��(���)���I�=3��k3����1v>���7pHO!,�pw��
�9�s �JI�'I��0�f^�^�J���|0���S�d��`��!9�]��..�B)����UaN��G��`�n����)5�	�E{��U�P
��Sj%�|\��U{C�U�e��y�9������o�����P
��(����kslBPwK��G��`77-��9����\p�7�^3=�+����M���Qk!�"i���;�$���� owrmw�����n�$/���8kyy��e\���;D�\w���R�J(�F��`q�8{J=RZo(��U����>�c-DR$�XR{'I�\��k3%�J\�e\��2M�J\����M��e\���;D�<��kZ1�n�P��Rq��hq�:�z���Pj}��o2�k�W����I����I��C����*q�q���4q�p94��cy�k�r\�e\��[6��&u&n��Ro�j�>�A)�VJM�bh�W�f�I2��b-�Q��Q{%��v�v���������2.�����~Rw�M������rm._�@����L�:K!UB���7j��S�Bi������e��s��%��4=�� i���;I�u�rl��NRH�������e��U��.h�l�����\v����+���8�J(�F����x�o'PZ��B��Z������\�t����2�C��x��uKj����{j�����Sy��eo.�;p".�p}Kj����h!i���+`�E$ER..vyy�*q�����BGl��$\��o�5M�f
�2�C�J: )��-���$\.�Q==U��7��%����p��[�P�2�[��J����}HZ���N�p��[;y�X��1g�/N���p��I���h��h/���L�uEC��$�[R{'I�.\�fy���i�.�wY�D=]?��I���T]��k���:�A���2S���8���[T��n;��Z�Deo*�3������[��Z�e�>�*39���y��u;j�$������9L<���M�=�8�����{/�z��f�T��8��d������������T��}�C�"T&�9C3y���k��^]���(�V�����[�a�yRg��JT�u��$�1qTN*�nw����(�;rD/��(����zM��Rf�Deo*�g���	*�n�����Lu��
����I�Ufz$UG����W�I�=����sH���Me�����v�P�t�{,��^�zuo�T��K�/�Q�-*J�r�!�,���d������d��gE�W/����s���{�Gq�lG��$����V<�l�Deo*���L��S����"�����n�������9j�$��|X��
�Y���}��8�'����S�����7�v���27���^=�bh���I�� �"��]r��E��*�g�0Y��*T>S�m���m����AW[e6IUHqG+v�^I'���*;x������N�$+{P9v���T�����%���y�y��b�a�R_���g��#��#��R�
��UT��a���Q{%I�.T�m'���Q���L��������*Q�Q����6W��k���[%���T������@Z��R���^>����P�Y1��"��-����[./�f[%.�2.{v�n%.wA����q���<qs�p��E��J�Q�}�T�PZy(5�.���$E��%�w���``�fn��^��}�>\�e��U���[(t��P�e\�e��eS�5��n��*������6�:�A)�VJMw�M����2�E�o�x��u;j�$����.Z�������I����^U9U�2*�r�=�m��m�mR%���T;4�)�f�#ka��B{&r�A��;����v48��>~m��nO���k��m���Kem��T��E�L��b4hc!��c�E��2�E��$E��%�w�|���)�.�J\�����E<`�\&����x[�q�>����`8����������o�L�������Ku�\��d�B�bhfu��9��8���(��l1�s�aT���JT�t���}n���P�l[����LQ���J��!)��o��o�]�IG[%.{sY��:\.�����@7�"�ue��Sm����(�6{�=���(�F��93Y���\?g�I��5^������T��"�:,"��h���+I�u����b����,o�p�y*�n�^��z2��W��s��BQ%�b�G���#+!���}�x����L�������I^�;�Z���(��$[L�`��j[�l������E<g�8�2L&���!�k��o���`^��(��?�l'5��JL�e�x����:e�\p���U��LO��*2y�b(��k�����,nZ�Ub�/���t��)�dr���`�Za��Y�]�]fzuX�8��U;j�$����K�m[�U��7���tN�)Ce����>�fRM��PE���g[�5d�L�1�L[L�k�x���i:e�L�-r ��:h�U���+�(��l1����T���=��N���d�d��gE��+�<�vfF��(�����42�u�V���T��j�:�A�D�V|��L������\����(�V��=�N���I�9�������$'{@�����R%(�2(�n �f��8��}�J����#0��(��G�Ou��S�L���LB(�VL�=��Z ��I,[% 2 ���m% w�
r�d@�B���\��A�k%��1T���&��c�?C��������9V[%�B(��������S'r�J@d@v	2��J@��!�\B2 ���h��gh��k!��T�k��D�*�7����u��:[ Ufz�'������Q{$I��ulB��^+A���eP�][������,U�������|�-��b�Q�!U�����js�R��h�q�z#�In�N���P���i[
���c���]�L���{����1�q�-"m�V��n��*1����o"�C14o�[3=�3��+0�m�1l�
�0��7u/���(�"��*A�������K@�l{�<&m��(����=�d[(����c�}Ay/���@��K@�l{�`��B�l;���R%�� B��U�Z��k�s�}t���!Jup\�$�BG���U��� g�-��(��?wk��a�N���(�sKu�\�d�B*�fZ���FaF���g[c��]�V	��P�����K@��l�����T�.UG��W�QQ�m�d{n�'q\o����dqC�p��lH&�:F�z�B�-@�*ADA�\[y��&Y�Q|���8$�%Y��8���������2�)��oS%��(��k+���$����!UB�/��o�� ����E��u��:�v�5���zz@�y$�������=M��4nn\'v��zCpL�u���y����(��?�i[m�V	��H��	�S��Z
�T���0^/���c&�i�����I�=7��"B�p
�#���q���1yv�X�1&N��*@�<[q�5�x��Yc?����)c����`�a���,�Q%�V�h�I'y�������,�8����E���\y��!�V�4�����H�5�	Yy����?7���Q8��p���<����.E:�O�e��GEOSDkF��H2���V����=��.��D��pry���_�i�m���mk<��kB/��^+��
8���9���I�gq��Z���38���$9���v��[�����\<��/�lDvs��y�Z�p��S�$�hr$�����xj�:�k�(�"�V�p
��sj�%Y��A\��U�i������n8�f����S�����\<��/��-����Z�������8������(t<��i���t���Q�Ms@�S0�������J���u
C��?���O�3m�*p^���
���������h���i�L�:�,U���T��������xZo<�������C��?6�N���K����I��!U�i������n8�f�.[�����\<��/��-����&u�Y��?8���8����xZ����S���t���[����pZ<��X�u=��&>`5Y��
gp��[%�����[�����\<��/��-p86���bH����<'\���[�i5�����hX3�������}mR����pZ<��Xz����96��}[���v>��_U�����3Y�=��{8�fO��*��
8���9������1jK��J�#�|8�+��y�L�u����6	�AK[����pZ<��X�u}�<-*�����g���:p.g�n�k}����n��?6�N���K�����]�V����N��R8��3Y�����l��n��?6�N���K���������;�u���N�#ut�����=N����z����%�Z�\@)�O�=�Nr��a���T����fq�������L�u�����aR[����S0-S{*��h�G5��J���|4���;�Z/�fr���`������R����`Z<��T�s=�,o���-�G��R���Rh&������ c:�)��)9�,9�z�pPg��J���|4�;�w��S���"�b"r�1���
�
1����k>��Y���2v\�w�N<���H�bh.8�j��R]�[b�����#xR����`Z<��T:��'�9�[��J���|4���t�S������.vP'xn��G��i���SI��@���9��{�Y<��� �b`&���K�A�|��?2�L����JR��G�=��k6�0�G�t�S����`���[�9��)��r��r�i��,0c���xxO��=��L�-r,&!{y�B��w7L+���JR���^'"�+�:V����]/��[�3 ��bn�.�m�<o���vn���5��r-�R,��R{)�����<����cGfdFfGT���e�4[�JdFfd.��@���KnZ�M�V	��0O��-N�S���L��N��j,Uf}���K��jK��$���yl���L)���QIf��94���f�*������17S�Ym��L�a*��q@Lk���`j�g�����k�G�J�!)�V.���d\.O���m���
����PIb��94S�d�*������17W+w��m�`�
S���:0���������l��>�8`)������C�N��&U"32#�o�i�V!sh&qc�����K�?ss5s��u��J0�����S��i���x��Q?L2�i�C����K+��^JR�������%S�5������J2�����]&�Jd�u���>�/_��[�h��%hGxo�`���u�(b:�)�f��`X3����j�I�t�������K+��^J/)��2�I�[%2{�Y�T3�!s!2�r��L�,��]T���J,�R,%��#��;m���������T����L��b<h�!���:�+Ufz/��bK��rK��$���Y�?�\�d�/���4�!s!2�r]���C���[e&K��1|od`)��n����\2�N�9U"�3����@�I��-5^�;y�W�i���>���L,���-�����`h������T���n_7h��T����\p�U�fz'd��A�4���_��_�"i���;�$���� �|�U��7��I���q���3��
A\��Uf�j�`@R$�\R{'I�\��k����,��8(��I��G������ Re�^�w�II�����������Ne����|Ge��oG�W��r�`��#���I������!�.�J\���x����:��L�-r(����a�2�����HJ���/:����n���y�^��$��H�%�T�����gh��pSe&I��"{��D��%�w�I�=�����6��_\���x*���<��L�����z�����m��
CR$%��!�Z���;)�J\���x"���<��L��=l�3��F
m�}pGq�|��_��ef1�:3\���x��Y<��L�����z�(��Uf�X���HJ���/���pS%.;sy��9���I�E��,�&�V���n��'���$\���r��	�V���n_7������\>W�m�%�m�'���li������e��XZ���R:������g�����������J2������`�DfdF�����L��F�+%��7H��&G��&��'���R���Q\��Ufz,���{&�bi���KI�u!s�����JdFfd�-3��*d��	�����K�?ss5rqG��L�a*��1Z���>1�7���R�E^x�)����p�������$\*O�����q�=�L���N����q���;qs5qUJ(�G���I��R(�>��n��E�����u�q6����W�|�@�uC0ys�v��e\�e�P���]�����R%.�2.��w �fk��Z�a��R_�j�>�A)�VJ���:��*3=���\NX���[j/%����M���
���EfdFf?T����vh�Y��q�Df_������Z�������-�l[*��nL����L�4{0��Z���h�$W0��l�U����H��D��%�w�K�=���2�.�J\���v�R.�2	���R�;�\�.��pSe��P��
��vI��$��py��;dJ����em.u���B\&�^1���-�v�[e��`�q,�R2.2�N����M���MfmS�T����L�-t�*�:�6U���`DR$%���.O��}H�����I��R.�2	�����(C������8J�E���SP���=��n�fq�:\.������l7���
����GqG����o��rl�N�~�U��/���{�C�BT&����r5Cl�xR�V�����r@R$�\R{'���.�f��C�S%.{sY��=qz).�pw������M��.B�������Kj�$	����A�[%.{sY�y�S�p���[R�fr���9D�(��n1�����?��krT���xl���9��L�-r(�&���n�8����h}��+I�5��Y��eK��e��We�����wJQ��t�M�Ju�n����E�K|���x����CR$�\R{'���s�<GunB��eo.���L��S��$��`}ZN�w���:�!I��jI��$��pY��k%.{sY<{g���R\&��
��&�]���0�Q��������}��i�L�k�x���^`2������&yi��kmY����GR$�\R{'I�\^y�����\��SwfN�)�en�C��.4�#��8J�E��}9�N��-*�UY<sg69s�On[u�n[�19A<�l���X)��=�N���Y�������C��|r�p����eu��\}H�dH~���6S���F���U�~.�'�x��lr�$�z���8j�FW�[���H��pH��$��`y��V��!�eX>9�4o+ay����oA��n:o��o��*}?���T�uc69-I=JZo �>�<��4U�~,�H���I����|�U�~�a�O�2��JX��"��E�2,�2
�r%�E�t���\�ORm7�T��.�XI�����,��h�)�z������yR���J�9,���Y��[	�CP��I��rX���Y�|�ny��$~[����|�j�>�!��'�@�z#��6��|4���h^��Q�Q{"��Fv������*!����dH�q['�����*!������H�|��my�qh��u��J�r��Yk�:E��QTka�����l��(w"QDA�kw�����I�:-�n�����I$y��2H&�����:m4n��K ��!�g�l���qR��J���X�:E��,a�|�R����Y]��*i�(��m+���$/�6�������$��9
��Z���i0j�6Ufj���e�A�uCj�$���s�w��6,�dy�>�T�e�L�-t,����$�J�^n�V��=�d[#{�&�d/������v@&�~�Pu%n���x
����J�
�J���i�
�2��N��dmvv��2@&�����c�F�]�>m/>�=/�`�nF��$�z@��
}��*@y'������A�l�{0�.T�{un���P�(���=�d[(�G��}�
emvv�e�O,���0^�0�G+L��J��<����)j������,�u�p�M! �i���Y:���V	�^n�VG�=�dZ�q�i��M77��^w��d2��	�2]��tKl�(L�L��
�d����� ���(�*���a9��2�v�/O�&9�N�2-�$�(��kA�s�A����}�,�s@�����5^�0EuB��=�J��:�Z��n��_�8��Y8"��I�{/�z��|��Q�0
����Zc�Gq�����#~6���
���L�-r$�������J�%��h���#I�5��i�6w~������}v��b�������"��q��p�t+>�ka�'�r�B���Z{��?6�N���K')��8�A|;�V�i�����-I��8�&L�����Hgp.��r6"�y����<�����8���;�T��xZ<5]��5� ���V�p
��sj�%Y���(nh�V�i������n8�&N�[����Hgp.��h�������J�����8�����O���xj�]M��xp�Z�l�)���=�d]8���[�����|z�i�V��:W]6�*��4p��q�|��ny�NM�kl����<��k��MO����xj�cc���g[����pZ<��X�u}�<��)F&1�38����n8�&��&�[�����\<��/��-����G���U���T[]���O����Gl5��N
K�����i���cI�������J�#
����8������EkDl��G8�s�8_�@c�<O�&,�l����<��}��S<�O5M��LO��T��LQm��J�cN��xN����uO�sl�A��J�#�|8k}���}�L�u�����b���w<��)��t���IW�=v��?������;��s)8�t��Z����,o6�M��)Y�DY��E�$g��?����v�R��Rp&���w}�%�#�?2�L����J��������h9J�!�A��R8��3I���)���|�J�cN��xN��$�:��k��]�V���gmr`��Rp.8���|s�T�]�0MZ[p��?2�L����J'I��4/����hB8�Y����������o����0����J�#L��xL��$�����k/�����!��I���������;j�.y�m"�V�d�)���=��\4����[��qv>��9���'�����{8��E��S|gN�S0%��&�Z���
����8;��9<�sx�������W���q�[����`Z<��T�s�i��6jK'�J���|4���)���s��Ks���\/vX���\0Sr����s�<���#[�;�Y<�'p�O14�sw���s�4�*��0��1�������qT���J���|4�G���)�fr��0^�0����T�d�)���=��\4���R��qv>���|�C39w�p�]�0F�P�T�d�)���=��\4����T�����(�9�����{/�x�(oF>�9��)9�49�x���f-�?���x�O�8���N�r�!�U���u��3��;��L"���X�K���^J'��2wM��17U"32#�o����l/sl�����*������17�s�t��N����7N�s*��9�p���z���n�ihs�����W-��K���^Jr������cos���������$3���v�>�����K�?s�|�E��|���9���T<�=Z��>1�7���[�5m���*3�0���X���[j/%)�������V������e��[���	���-U"32#s��bn�������/�p��SqC�x�T@8���z�������4�*3}�(Z�
�K���^Jr���E>�r�������df2��<7w��	 32#3���1
��5h/�.�p��S��p����i������yQ���K�K}7
h�V#��&8������J2���A������K�?s��sC�iz)�So�j�>��)�f���fka�'C{*rE�Q^86�Z86Dqh��X��U[j/���{v�;���*�������T����L����h�$����ec����V\��b)�Vm����\2Or����\d�-�v�G�C�Bd&����M��P��2���nB>c)��m����\2��<5w�v>%2��Y�o�����I��.�MB�ZP�*�K���{��k,� �(9�;Q�w~r���u��2�r-,�^2���l����I���������l���Mf��T����\p�U�f�]�x}C/��Uf�`T�#�$�\R{'�d���<h�l����em
`���B\&���%�k�QN�c����0$ER..����y�*q����a�����2	w��kZy�B�i��������HZ���N�p�<��������,��>`C\&����zMC��
Re.I�^�Z��HZ���N�p]�,���5{���,�s���e�oI�W4��*i���+�II�����~T�L����e�d���yJq����M�Ju���
c��
K��~��c���@R$-]R{'�$�s��-������,��s�kQ\&����zE���b��W��8���[T�B�V������,���qO).�o�^��z�nR����L���I�����_�<��#R%.{sY<���,�R\&��
���Em���L�����������Kj�$	���}+�Zkq������w~3�e?.�p�^��z�N>���vn�ER!�"i���;I�u�r?��$�J\���x���r��s%\����0��a���~/�DK�B,���-���I�=����\+�q�q��$f{�C��Z�a�DfdF�����L�<6��<����7N�#'{�#'��'��FS���C�n��*s�
���Z��XZ���R�s]�u*Y�DfdFf�2���B��,A�y�DfdF�����l��.h��.�p��S�|����i-��M����unn��s��fl���V.����\.Gy�D��f�q��@I^�wy�o�m��U�2.�r��"n�F��M�V	��(7������PZo(5��q���jRe.�gqh�B,���-�����B�.j�U"32#�o�i�V!sh�^[��U"32#s��bn��24���T��(�&��:(���c����aQ�2�K1�`��su��rI��$��p9v�|�.�|\\�e\�%y������9=[%.�2.��y ��k�q���L}a�}�T�`�=�j������������
[e�Wb����X���[j/���{r���T"f[)��{]�~��:\.�e2�7E�%�2nPO�H�y.bwa���I+���I��;q_���}��M�Nu�\��$���`�9n��G���a�q,�R2.2���+��U"�7���Q��kd&�z��x�����F��N'{���R,��R{)I�.d�;u1p�Dfg2�<���2�r-,5�XA�����������lR���[j/%)���q��C��2��Y�������N���L��W��'\m��CR$%�>J�=�����l�T���6�$��r!*�ow���
q�7ar�^f�.�")����;��,[%.{sY<}�X�R\&����u
���K�y.b����8Z���J�o���^-f�T���8�{���RT&��
�����b���"���(��nQ�������k~*�UY<B�����L�����
�I�L�����af�V�����[*��.�}�]�Qy���9<����r��V�]��L�����V>�������2��(�V����N���U����b�/��x8����5^������T�kd�
�")��?�&yf�����\��9`S7\&����z��M2�*�\s�pG����Yeq����}�,��3p�N)*�nw����	����I��jI��$�:p9����T���\3����.�q�����������T��S B��(�V�����[*����M���Ke������P>U�m�5�m���^n���@S��h���#�$���N�9t�z����CI��=������U�2(�r�
�m�o�Q<��R���$�M�DR���H�����Km������=��H����I����E�z�*a�a�1��oka�Ww�I����\t������+��J$�%�xR�hrR:�z���Hj�	M;j�(��\��#�E@Z7��L�o]�Zy��6W��a��8IZ�g94����U�2,�r�mn�n�h��K)���T�Zn�~s9,���zC��>�a_���@
�@��W@���Ee9U�2,��c�i�V�rh�I;Pk��eX�����l-�)j	�R���,��������K�
��[�w�f]�#����9�F�f�I���l����>�$)���E�w����dH�dZ�%+�q�J�������h!i%����dka�'C{*r�B����*aFa�m������Q��)^[%({BY[^��@����W������r��42�aFa�l��P��6�w��e_,k���:X.�e���K��aQ
�4 BI��'[c��I��6U��'��w����@�\[����W�E�[e����\ R�-,b����C��eW,O����`�����.]X�!S_��l��������'�l��� �K�4�y��]�X����o�����\X.XXw��2�V��1��(�V��=�NR��I^����m�!y����T�e�L��}Kl+����[%/�`FI��'[k���Z��qF�x���	���L��{��Zyn�2\�Q��	��5����,U��/������e��kF�W��'�n�y.��� 
�$[H��l�:�6UB�'��so&��)�dr��0^��v[%��Q�m���z9o��#�\�@y'��:G��r��V�U��2=��:���.�Y�,��Pu��\{n����K���d���� d2�kB�*����L��%� 
��ZH��j;�[�*!����9���dr�kD�O�6�[%��PRm���z�X'�����u�x\��q9��L��=,(�fY���Uf����Q%���'��Ik%(�BY\n�g�(�A�l����(���2-���(��m����(�������K���,���~���M���������^@���U���5t�5��I�U�p
��sj����{z�;���V���38���$9���1�}�T���38���9��L�<6���p)�?<�'�xf�lrf$�����j�Z�o�V�]j��?6�N���K����f	�l��G8���q��[�����M��?�������Z��y��������U<4���8�Q�
��[��M�����J�cN��xN��$�����qn�����i�V�slFy���m�08�38���h���i����������8���6u���!j5��PMwm��0�	g��?6�N���K����Q�����Hgp>=��v��96c�me�U�i����|����<
]�E�
.�����D�~ �:DE������}�wj� U�p
��sj�%i��C/��V�i������n8�f
�F�[�����\<��/��-���5��f�]J����*��9_?�Q�5_@�@[3=�S���wM?i�q��?6�N���K/i��8k�1������h��}���Rh&�^�
�:��0��I[����pZ<��X�t]�<�G�l��G��pw����E����95��&!�CzR����pZ<��X�u]�<,j#"U�i��Y��������������qT�n��?6�N���K����Y�B�U�i��y�>�T���L�-t�CBrP[�����S8-�S{,��>p����Z���gq���_T����=N����~m6^�dL��)��t-�����i�l�����h����:h.�fr��0]��7� .kZ+��0��1����kNsJ���P�*�����,�G�p�z14�sw{f��A��2�R(%��&�Z����[��qv>���,%�fR�sL��;�E��&U�`
��cjO%9�����n��?��G�x��I<��L��}Kl�:Dq4n��G��i���SI��@�(� FZi�9�����a���Ju����!�97U�`
��cjO���{n�[�<����8;������|��������:�����WcP
�����\c�i@N�x���A>��L�����R���A��?2�L����Jr���^='"U�g��Y<�g��bh&���K��iS0Sr�ir�1�]�.`��?��F�W���k���R�I=#"U�`
��cjO%9��m+��my���f����h��\1W|��LO��Z��S����\M�Q=�v�R,��R{)�����<w�������}�|�df{�c3���l�����\z�������M+�s)�So�jo#R��i����`Iq��V���y-M�X��U[j/%9����s�JdFfd�-3��*d��(.��*������17�s�4C'6��R8����[J��S8�>��]����~�UK{,���-�����B�N~���{������n�$3���6i�m[%2#32�� �fz.�f7�����7N�W��N���hj�E���s�Re�O`���K��jK��$���y��mp�l�� 32#�*���2�f���*������17[;����K)�z�T�{��~�e8���z���N����K��>���2b)�Vm����\2G���*�������s��96� o�0dj� 32#3���1m�~5�J���Z3>��)�f���fka�'C{*r5
BTg���\����K���/�^r��e��]p:��q������j�e2�wI�w!A��J,�R,%��#�Z�<�SR%2{�Y�y�����{ge�,��A���X�o�Z��ai���KI��!�3.�sY[���p�����.���?K]|�*3}�h�Z��XZ���R�q]����-[%2;�9����d&�ZXj�����������X��U[j/%)���}����������Nu�\���\uo���b�L�[%�")��q%������f5��J\���6�;��r!.�pw�L��
m�]�V������rF�����[��T�5|G�@�\�w���x#���n{�F�&�	����h����Q) �tlI��$�:p9��i��Y����,�J��J��e�k�l�5$�u`�l���d�")	���y��)��jD.�vYk��:\��e��0>�����k%�$ER�	�����'$�f'$p����.�\���$\��Z�5;��iZ�3~�70^��B��%�w��k��e��{,����EoxL��BPm�U�k���=av�Z��V��T�V��H��CKj���d;��Y�C�V��7��i<�i<��L�}���I��[���F���[��I�thI��$��pY���T��7��I<�I<��L�}��/��������
�II�3$\k�7��Y��eo.�sx>p8�I��%5=��B^��aK��aj���W���Kj�$	��������d���/���d?&�n_$������&�j�&,o���I�thI��$�:p9_Z�V��7���;�b���ZN�r�	W�"�.l���=�pn�k�Z�jm�Q-r�"��n���N2��2_������}��*��d�6u_o�DfdF�����F�����R�R8���8j2Y���S���M�����X:���R�s�����:���#32#�#*��d�����V�!32#3���1M����g�v.���T���,����ON����7�Y�V*�<�z����H:���N�r}��nk5u�Q��0IZ���*�j%.�2.��w �6k��c��J(�E����:(���C��U�kX�V�]����.,�*Gw�^I����+�����I�2.��(I�\^O�:�R�����}����Z�i����`�S�����[��tL����7���wo��@��BR$\R{'��>\�4[j%.�2.{v�6� .��v�Z�����}w�����1�[j)���T��W��L��R�����7C�V4kDu�o�l�6���w!�b����K�%��.s����/��^f���?��!3)�������M�G���/q���4$\R{'��.\�����Q������R����L�}���/����6��VA�S���Kj�$�����zg�]���\�~��:\��en�Gw�����+I*~-�_K$E��%�w�����5�E��w%.�ryqX/�L����������Je��!���K���^J2���-d�Jd�&�����!s'2w�r������i����{�����>��}�����N��*�aY�-H��}�����?0��I���>e&n����VFTF�m�.����[._Q�I�T��7��1�+c�{q�����������p�C1�l���|�����}��m+�?p4�I��5>_v�W����V�-")��oq�?�:��&K��eo.�;�W���2	��Gb{����NA�l�����8J�E�?T����wn&���8}ge�N/*w�n��e�k��0>� �����~�F)DR$ZR{'����]N���|�R���\g������e�k�lO1���Z��!�8�<3�Gw�^I����r�Z���T'��L��Ee����>�p�gtKe���]$ER�-.�)���6w��sY���2y��I�o����y�w���V��o�"o��tpI��$�:p����������q���_���e?.�p_$�g����*��$U��DN3 ����;I���r�nl�pq�������d�,Op���2�����<�lo�b��F����=�N���(g�
���H�dHva$9�����Z	�������]CV{��I=I*��LJ"�GI�
���tw�I������x�cCj�$������-������e����|���R	���]����Gw��R,�e�87}3����-7�_E���1�R��!�3R������G�|�e�l��j{(�2(�P����t�(�JPeP���@�m���qfz-ERO�j��J�"�������S�E�lv��z�"��tlH��$��`Y�KX+a�a�1��oa9�3�S������L�kI�p��5�R,�e�xo���_�X:����R�K��M|vW6z�[�!�Q{$��.P>�N���S���>�$){@9�/�R#\ �!��m�������oK)�z�Tk}�Q���H�.�j��������Y�v�O��0
�0��G0N�vv��^���=�������@�l�
D4C�������W�������] R{&I�.X>��nK%,�bY;�]�`�������v�U�t�6�t�@
��[X�S����2X~���1�?������!5��@����` 
� J����f[���A���H��{ie�����%�W+~��?�"��p�Q�-(�@9Gu�y�eO(k������d�gT=���{a{t����������FatdF��t�m�FyKj�-���em�t��>P&����������������F������#I�u��!^�V+A���<��y���L�}���d[��Q%�B��Mb�~���~H~G��K��Ar$�k�>���������p�tA(��j����}(����dq���NH&���Hl�*l��[�����0J��?�����]�e�eq���0�NP�8�j���5�^XVX�"��Z��'p����Q�Q{$�d��QN�������eq���(�NP&���������i���`F���g[k�O����,�����	�d��`}\An|b��??�z^�0:6��H�m�|���j�mA�-����1:��L�}����(�b��o`F�������Oj%(�B�i8��	�d��������6m��6!j� 
��#jO$�����_W����%Y{����~�#t�:�<t�r��g�&���c���>��[�Y{�Z�u*��=�Nr��<�a9��w����������l���M���T�_k��������o����&�[K�/��L'I~��ML��qC����=,��R�m�)���=��]8o�^����Z�gx�g�c���c�x����<��<?��o��]�S|�Z���g�8P�0���^M7��^b��x�=�R�u*��=�$^'<������������<��X�����Z�gx���?���O���xhW�=���|�j����S2u��jz���5W��uW�_�
��j�%���)�b�J�k
���i�����m����Z�gx���?���O���]��a�����{������L7��^j����K�����:��\�x]��8x���x��;
�\�������?���O���C<V���_ ��*�J��/DL��v!U�.l��h�I�Wva��Y+��@�@���K�����3 ���+����x;������L��T�����W=�J��PuP��$�����.������p��p&�v����7%�&-.�J��PuP��$���������Z��gq����?V������g�K�����NwP��;S��gx�����+u�����ND&W��P*��@�@�������m��V�_k��,n�>������_����m��l����rZ*��
8��8���I���Sl.���W�|8k����b�����S�sj��s�b�.��)Yw��k��)�|��C����'��;�������Al����map
�d������E{�Z��������?��g��{N��@���c'���)��u'�����.
�Z���������A�����S���|:��t��)Yw��k}tX�S+����p����������,u�>��[���%8�S8%����;7��;�9����l�g��{N��?��=;�0�)��u'���8�K��[*����pG�����g���0>�pn�D����p
�d�y��1�I�`��`�gq���=�3Y�=�����rZ*��
8��8������u��0��a���%������d���������n���6�N��K������K��u6�����b������E=���9�'��r��{Jb^+�h���kjo���;������jB06c36����lo��E;'U+�����.Q��{�w�����7P�����DK@�
������o>���Ke���V��y�)�����d]6�Q��Tb36c�w�i�a����w%6c36��� �6���*�&����
Tqx�.��q@7��_8&�Z�/��iFS4^S{+��.l>u�n��fl�f�6����5�}w!2#32��� �6���p�K�)To�j�8K�������M�"���"�GQ��M�tpM��$���9���c��������
Kr���k��fs��fl����D�fm�C��{W��7N�[���o=��q87�Z��X�I����+�C}Ev�)������$]6����-�������f��C�|o��~������Buq���/�B�Z
��@��R)u�
��!�j����>�3i�:XN�-�]��%���$;�MG���J/Ywr����uK%6{�Y�}R����I������������F&�_���/&��i���[I�ua�����J%6{�Y��K6wc3I����7T�4�����jV�7�o��)������$]6o���:���Vf�Z�R����L������#A�g�T��T�D�h:���V�s]���7��Jlvfsp�����$]M��.������#d�kyb)�n����\2�Y��Tb�7����Nfc39�KM�������wXC�4�ke+K����XJ�E�?�Z8��K%2{�Y��]����I�o����"^K^+y3��XJ��#�Z���X������.�C�3C��q�����O�E�<o�l�/_���X���[j/%������\*�������R����L�}-������,l�n���5K�ttK��$�:�y����Z���d�t�*8d&�z����CZUKKe����v.,���-�����@�5j&j%2{�Y���������\��R��C�=���
�G��b����K�tpK��t�r��9F�&�R���d��|�rd&�z�����&���������_���txK��$�:�y������d~/�8��2�r�[j|�a]����KRw%,���-�����@������Jd�&�8�'3���I��9�=���L���Q��ub)�n����\2�U�[*���������q@fR����g�M�#�5�����X���\d�s���[�R���d�>��/H]�/.�@��R��}�},��6Y���O ����h���kjo���;���.���	�������$7��������������BuE�5D���Z
��@��I�:@�	�����5�x���l�Y�^fFJb����KI�u!�����q�q���4tqy;�[�j%2#32��} �6j�n����O)���T��*N��t�hj}{�~���#�4r?#��/����\._�
s�DfdFf�2��B�5����������{����(��!���f�N}q�}v�N�t�hj}Ic��J�F��.����*,���-�����@�Un�����������J2���"�Z�������������xu�S
��8�n_.up
�DS���S9�+}1���]��h:���V�t�|�i�]`������LKw��S�n�Tb36cs�]�n�6��C�QK����p�P�7�S�������}&����<mi5-��+�K�ttK����t��y
WR��}��}t������������dJ[Iz��H���Kj�$����P���Jd�%����:d�Ff2�78�(ke�����Je��8/�{Y��K��^JR�������V"�/��#����������^)7ob�`�R,�R{)I�d.�uW�{,���J����� ���\��;��kje�����_{��txK��$�������Jd�%������tQd&�~i�z�����
GT%-��������K�tpK��t�r��Y��������e��h���n\&��}��
���V�bL�M5	I�ttI��$�:py�������#�{����h!3��������m<��m"����I�ttI��$�:p�P��9Z�y��/U�~��:T�Fe����>� �7>p8��1/��kG�w�^I�������j��j5����,�����e����>�p���Je����*��K��^J2��e�q����l��l�~\�8�j��J]���D������,/��K��^J'wr��,sBfw2��y"�y�������O5���
g�8���XJ�E�����.{sY�������d�����j���{���9��2�"����;I�u���h/ij%.�rY�������$��`}�l������R�b�.�R,�R{)���2���[�J%2;�Yl�_���������`|��A7.����K�tpK��$�:�9���Z���d��$��<�<Y�]�����t<�\u���ttJ��t�qg����~�R	����a���$f0���A������y�������p����Z���4P&��h�S�q���,_��~�l��o��8��Sj%)���z�X�f`f�0���K>|�:����r;�t�
��N��M}i*�YO&s������S��j�S
����C��x9���Cj�$��jwo�(�2({Q����kS/�)�����u��x�����e��6<�X��Rmu��R,?����(O�l6]��H(�R{(��.`>y/�����a��;����\*������!�]#�G��R4���v�r�CS4?�_7����������P
�cSj%)����^�X*����5��r�y�7?��6?303���-���^�<�h�KS�w�CS4mL5J��F���4��{����/e�Q�Q{$�$��Q��.������P����:P�e���G��d|�6���F_Ln_�R(%��;�a����w%0��Y��K0�3�1S���2�����F�����R R{&I�.X��:x�T��'���{���^X&�vzn����q�������3I������j%0��y�0X/�L�5���29����K�H����fY��S*a�����R����q�UO���f�e��M�]��!8��0J��?P�2��]���QR�@����o��|�����F \����Q�Q{$��P��z �T��'����+���A�l�Z2�Q>����@J��!�����w�f�w`�%����R���L�}���E����:Y&��_�R �R{&��X��|��Zi���eqN����nX&��}��j��&��(�v�lkLrNj�-����eq�����e��W�j{�J]�����q�{J%M R�����s��g�"��+5g,��vV��t�2���a����V6�Z&�[��H���I���7�����E����eq���i�2��9��3�6u�y�l����<aFg�I����K��P*A������A;��L�}���M��n���aF�����Q�����,�x;�����P&��}�3e�����`@
���	�����Em9�JX�9�k�������d��oIv�������]F�\c��=�S�?W ����9���~�!�ZZ}J�?��h�O~�����O)~�'~������m��tC�k����?��y����C�#�����Q����k�T�R�C<���g+����)kO���'~������~���Z��V�'z�'M�~�KK
��������'���L�N�!��G0�q������"�R�C<���G+������^�)�O��O��?���!����Z�����4�/-gX/m���=�i���\�z4���W���|�K��XJ����dF'~.��J�?��h�O{?sX���Z�����4�/-k8��XkA�z���Rz�7n�3���������s+�!��?Z��>�\���O)~�'~���i���/�Z�����4�/-[HQ����=�"z�u�^����s6Z������p���yJ����^2��~��J�����Kz�xg|��%n]��x�wxGZD����]������??��=J~��g{���)��\J���������&j)~��S��K~��g{7��KKO)�!����?~��xJ�������+u�Ibt ��A�%�$�����x�Gb��?���J�������R��$�%��Ze%�=��x��)�;��;�"z>z�Q~#]J������R���Es���l�C�ZK����������.�K)z��S��3��h�����,���h�vhGV��'+nI>+XJ����Zg���'Y��;���b���h�vhGV���{��l�M:�=��)�'�O@V���z��y�{K)�������_�����������L9 -��?K]��n�G;�,��Z�vh�vdE�|�bL�D�R����g��8 +�{g�C[��x�oc��;�"z�����,�����OOq��'�'i��`�G{���Y�;����������sw�8�S�m�3���h���	�U���)�;��;�"z>zn��f����;=q���\���w����C�ySJ��������W�|�����Nv�3
���:x�:;���k����b7����#��Rji�Ao,���H�.���x.���<�<�5#y:�3�}�����	�����1	��[��U�7��"�7��X��,�#����g�R��?���V���4�B����6��8�1Y��XK�<��^3r�=�9�7|���	�����1	��KyIl*��E<o���R�I��G���E�����6q�b-m��"�����������(x�|�b)O�O{�H��x�p��`�V[��:;���ch�rj[�T��7��I�C�~�7��������V�E�c���������(x^�|����'x���f$O{<sI�rSqi�T�N����W�ITt�]�rH�����Z��&�����!^?���l���a��
�Oi+�V��P
��������8x�[�K)x�'x�kF���3�,�����	�����1�������x���Z��&�6�����2���������������Oa����V���]wp�����4��s	W���R������(u�IV����2�E�WK�<7��]G��cFV�������<�����Jx�����!,.rV,���4�5s�B��;�"Y���x��)Ooxj����C��IV�����?���9{�[s6u����>G��;��4Y��#��Qk)x:����(u�IV����,��_����N���.�;�#+�=�y��
��<�����+u�IV�Y�V��M7g��^�;���v��C�.;�#)�H�������D�|�����')��:�m��>t`k7t@|~���I��t�,�I[J����$��I��Ds���d��y�R����K#�.;�#'���La9�+�J)tz�S��w|��t������c������}����������D{:�(��]J�������Y�Ds�l�b���vW������P�
u�������Z�m��:��),�bW't���&���7��SX.������I���w!��9�EN����8B�)�Not��
N�������}e�K)m�����B�;r"9���M�|vkx�,t��STp2���h���)���#{��v���d��	�������k����3p��SQp2���hN��F�M�������/���.;�#%���\W��R
�������	�����o����XJ[�Q,6��B�;r"9���C��x����o��|`O����������g��!
����'we����o&��9�EN����;��<�<�5#w:�3�]������������������B/��Q�x���Y}q�\�7n��>�7}[�����W�a�;���oc����'�t)�N��Nk�H��\����dj)tB��t�zL�����
��#�Q�w��G�^�O��p��8�=�b@xJawi'kJ��]��R���zIN-�N��Nk�H����T'��R�����|��E���v���S�v���~n�?7�s������N�%d�J-m�Q��S
��������x�p�n�R�O�������%lY��XJ�<����cx��[H��W����M<�W����
x���.���79Oi��(���,u`v}�mLcq:cX�<���B'tB��e�N{:s��:k%p��p�zLb���3�K|����|yw��x��w�����]��S�Ci�P<��g�Y�v����;�s�W�8
����!�����<�����Kx������:�S-�������~�;�#)����!�e�R��E�v�Q��Nr�=v����'���F�]�����'`GN$':�3��/�N�R
������R��D{��/zH����)m�G��wJ��9��hOg
�!�y�������Q����h�����x�9��6�N����v`GN�&'����#���Z
����>�R������k��x3��
��l��;�A�B��]����\��:�:��)���Au�$%~��7b/������g�6�/��A��'I��pnIN��8}�)N+�L+%#�Sg}~%o�����c��#�/�F���'I���W�!��R
����v��:�$'Zcg�	���:s�6��8���3�����HJ4��D�UO�k�^"p��S�'��'@F4��z\�xL�V�z�U�^�\�udD2�8���f�@�{:������%hk��5Z��[�� �������������>2��l�I~SJ����H����N2�7�z������R��1�U��GU�;r�$9���c�{��:��)�$��M=t�v��W�U�����c�S<�wrP�H��Ds8c�Q[|O)p��SG�G@F4�������/NJi+��g(uPudD2�=�Y��RK��������qN2�����}����R��15�.`v�DR�:�K��x5���o�����::�����(~[��F+�tvQ�[�q�2vX�u�D)qz9�$��*�����iL����K��	j)r"��r�zLb���������/��nx���n�7�Y��y�J)�:���:{�����!�?��9�9i'N/gW��V�&n�����$$:�n�Q��G-���Nk��:����q���e89���Ki����o����H�����p3����J�R��M���8��9\��w!fb��f�zL�������K�c��;O�iW�:���q#���_�.(��c����X�u����8���/�������Iqz9s�b?�V�&n�����$$:�n=��Y?j��w�]�����n�`g}3����aKi�����_D��:��LC��]�+��"'r"'�����!����R���9������������6�Z�������_
�;O��v�6wa�u�}(����zH��6���73�����x�V��n�����R��������&	�\:����$��]J[����_�:�##����<w���R�������:�$%�[g}I���[kji+�������uXGJ�%%Z�ynzJ���D�|'����:�$%�[g}����Ji���4n/�!����������R�t���=����&	�wHg}�9����R��1�xs#	��h�f�<k�h��5_����R���C�p^������|�Mlt.�������C9��o��S����T����IMq.}d.=���9���)���Ki#��&�s]8g���^�5\��f-EMOj�G���������Y����)����������tHG>�#Z���c��R������-u�IB4��z�u:�sx���c�����H�$Dk7c�j8+���KMq4@d4���9��)�)��R��1�w�������|8G>�>��d��Z����g|`�=nv���K]�Un|:e���K�[�����1'	qr7�,�[Jq����������t�gT�C�k�)m������HB4w3���`k�-B���Mq��.��M���z�u����Ki��6��F�>��w��h�f�)�e]Kq����l��l��t���s��=�we���N#�9�#��O�x9�����L�d��d���t����|�Wnw�W�����=���p�th}FeOr�����O+T\���g$�� ���06z�&+��������!�
�T��C0�������=��`:I�s�bX�����������b����>C\���we����{�����%A�m���<�������1�����������:nH5=������OikR!�{R��$�z:����	<�=�4������;�gX�	���u���=�������9��z2�T��4fU������:���FT�KU��]�!����Bj����I��tk�~Oi+
�zz�i���M�;yW���������4y���zj�$<�=,�M�>�R���:zH5�1���	����Bj����I��t<�;u��V@��@���3��8����a��3<w���h��'j�z�{yKeKcFQ�w��PQu��jz�y�a�[P��-��>������V��������oo��j���]�38����n�����Q�weKcFQ�}�C�>�o{Q�T�r6�^h_�F���x��S����TH��T{0�$���>�sk����6#������������V�+�f��X�Z����TH��T{0���>B���[K{Xi3-��L��	�d^���^S�<�7gOikR!�{R��$��:2�����6#��gX��������N�/�jikR!�{R��$�:Z���)�a�M������������R�U{y����6 R�'�L2��W�������6#�Zc��t+�����	n��4= ��=��yki+P�{P��t�x'�����Z��:��g��t~����]����X�u�
�Z���T@�T{.��x�O1q�����<���Wf�w�3y�������z{��u�p�=��X�v=�|f�������f�Y�^����I�o��H��M����2P���K��=�[X6�f�Z��:��gq���L�Nx&���8~��{���Ji+P�{P��$���y�W��p��8he<P'+������*u����!b����������=��\:��S����'��=��yG����g����=QB�&^k^K{X�
���j�%y��2�=��iG����f����=�~�w���V�j���sI������$��=��y�����g����=QB�x��)�ae*�v�=��]<oI<J\K{Xg�����6Fu�3y����+y7�7���V�j���sI���s��vD)�a����8h����LqwQ�.���Y�i�Oi�.��46<���=���I��^�u���)������]�pIv����)�pWb36cs���n�<������J8���8�r��x	�97�Z�L�x����)�������Qt����-������]g�C�|����������'����������p��Sq��������Nm��Y���S���~��K#�)�����d]:G}zpl7=���]qIv����8��"32#s�}�n���:7�j76N���������S�tN����W9�a���Z�k2<�S���������������L[w��W��[*�����Na�Yc���V��GN�����M\p���q�����k��x�N-�S<�S����}����{j��RtFgt��3���>���_W��yWb36cs���n�<�e�.b��p��S��������������.l�����Zi�[���c��8�O���^K/Ywn�SH�v�Rt��������s:�v�q�U;���K���
��x��xJ��%�����+���j):{�Y�t~�����L�����5�)������x��xJ��%���|�:���Rg���R��]�L����o�PL{��O�OI���][��p.���Z��u������L�������-�����)m���4.<���=�����A�=����yJ����Zw���s������mu��D��k���;@S4%����;��)$���]��e�v}�:d�Bfr���=�C>��A-m��P^�m���kjo%9���%l�<���=�,���3x��I�o����N��X-ES4ES��I����K��[K������*�������Z�s�~����~�$/���h:���V�t=�����b�G���<;�y��������M�*�����
�MI�s$]k�SO�Rl�h�8����������j��J]�o��y�E����yN=�)��t���Iw����R��m'��L���f����>�peq/X-m��c��hJ����������R��m���L���f����>�������)��)Iw��k}"--bW��b�G���>;�}�������O=��tK)o��MI�s$]k�O}�����6�T���>}�L�u�q���I�O��6����4ES�.6���+�����Qfq��a1������2w�s��Cwa�������K�VK�<7��)��t��g�c����RtFgt����$;��|�����������Dv���WX�Si�N=r*��<,FX��GN�
���|sX�vOU�l�!h�������I�u`��K�~�JL�dL�j2M�!L�C>�@��dL��~�
D�V�6��U�F=1*Nc?,����GF�
����D�f�Z�K0<�S�������^sWb36c�w�i�a�����-�����������{,���Z	�9�I�N�t�pjz9��]_��BPu�,��;����Y�`�&c�_�i�a��Mm��JL�dL���@�mmWq�z��QO�jk���(�F���I��UJy��x��U@��������k�.-�>������]g��}����C�T-�����������{.j;�T��GN����N�X��9mM��]��{�}%��uS��W(���Q��0��;��W��Q��=��D�^����I��
5���m�b���	�M����[S�c�pk%6{��m����I������]�	<�mc�����x:���Z�u=��!��|J�����A�R��]�L���8o1�S|K%o�PE���g\c��S}�V*1�������:L��d���D����/M�Z��(��$������9��:
�<\`|=�j�UP��n�#^�'r�m�Y��P�P2m�L;��W�����=���*-u�������`{~a	�����l����b)����oHY��P*����������}�L�}�����k%��P�����T�.�m���4_"k;�K"w!2����U�
�ZI�K��t;G�5�y��t[*��������A9}�L�u�q���U�C�)ES4ESr�9��L�y���J%2{�Y���������j��7s�k���<�p�}Wg��J��a(��n���E������=�,����n�L��l��)�3�(���J�P%���j������jk%"{Y��s2Y��I�o��<Y���X+1C1�T;v�5>O�Vm$K�DdO"��tNf��!2����Z8.�3P*1C1�T;v�5�
��m-����H�K��s1G��I��>��(��P+1C1�T;v�5>5��m#�!]y,P������D�vQ��.c�����(� 
� J�m�ig'y7��JH�dHvJ��$%��|�u�Z���!��m5l[�U=��r�����`��d0$��3t� jz��
I64�(�������AH������!���L�v���%�
Z��dH��n[
�f�Z�����1_����/�Y�(�O�q���_������ 
� ��;@�v�s��4�Z	���NI�a;�GX�kpk%$C2$w�j ���5���9�T��'E��H�����C(:n5�)1.a�����FaF��h���r\�l���2(��S�i����S�~�JH�dH���@�m��M����V��'E�����
X(�V�q���E�1��k�>�m#�K#}�2�N;��K���M����38��s�i������&n��������oCu���9,�9�Z��5���(uh���m�i�h��@��F��+�
�$��_+y3� ��[0N+wr��&_��qu�+��C-��{ �\��?��?�[!�W�j%��(��k���$�%\���}JA���'w~���$[��_@����R	� 
�$����)�g8�8���dO$k�KK$�@2���3�9,b��� 
� J�<�_�p���O��"9k�~���H&�:�@~��W��|���(��(�v�dk|��8������d�����#$�l�
Q�n�o��1�+D��p(�(��m������+nK% {Y���|l�L�uL��uh�S
� 
����S��1�(�j[+��������.@&��}��bb��@�D;t��>�{��w6��Y�EZ���I�o��XB:���Z	�
�d��3�5�����@��d��t2�����z�j��TB(�B(�v�Lk=eL��.�cO�cq2cq�X�'Zm�R�k���<�����X+!B!�D�6��
��i[j% {Y�����d��`z�
��yke��B(�v�Lkr�`�J@��8'3����o��@B����B!B��CgZS�saVk�J@��8'3����o��@��h��Z	�
�d��3�)�9�C������|�0]P	����	5>����k%�B(��i����Gv�*�=X�{������e,�u���V:!��o��s��u�}$�R
I|�����:@T@��L'Iwz�Wql�S��Z�h���O�I��'�
�8��V������-��F�xi�:Oi�cFU�����lHT����A���nJaK���������:��d�{�->A-�a�A4DC4��1�����1�Z��Jh���?��������y��������?�kU�Qu��jz��� X5T��V������I��A����=����	��{� �
�x1V��a�4@��h��g�������S����QU�j��s�QuU�
��7=��W��������:��d�{}�����V@4@���K������gx���x����z�5�OU�����1���
G��U�Qu��jzz������V������I��A��kC*���DC4D������+�����h��1A�m������Z����QUq�����[PuU�U
����G�}"���#�C[�Oi�TAuT����{�&�I|�����6#������{�����X���f�Ql%��V������I��A����������Dk{	KD�B4���#�^�S�l��l<�=�P�P�'����hu:q-�a��H���o��.M�u���I�3�r�w����J��+�Z����=�=��	���~3M�5A����=$X{C����:@T@��Lr��� �(�=��y�����3��+P�3��
�f[Nq�X-�am@*�@�=�N��@�a���Oi+mF��^����3M�uN��A�=���������:��\�x=������=�����G��w4������CO@=�=�
H��H�����E�I,4%��}���{����lHl!gqgC-�am@*�@�=�d^{���V�Z��J�hq�Od�O7@�y�>����=���Z����TH�T{0�������-�=����}`s
@�y�"U�|���bz@"�}�PJ{X�
��j���;;���yki+mF���A��A�M�}��G$R�.��[K{X�
��j&����.�RK{Xi3-�
��
�h2���="��).�������:��`�y������/�ji+mF���A��A�M�}��G$R���OOikR!uR��$�z:��������6!�I��
�d��`{D���~��G� R}~m��j&����:y�����fZ�,F��<W�]�c���S~�p$qsC-m�K�����:���^:I�����E�����3>���?&����3l���������<B?������<[��<����*��L32!�'��FT�#�[��8���6zu(/�QuQ��$�z�y�!��,�������gZ�C�|���NZ�JtFgt�A�m��=O��[J!�#��H�d1R}�:nD��g_������f����[�xG�9'yo)�g|�g�>����3�����T�3:���o����
�Z
�I����R!u��j{���]n��R^�!*��o!�������������������x����x��V�3:���o���N]�����T��>�%RG!u��j{��.�~�Z��1yq����j�%����)���j)>�3>����>�a��������<B?����C<��#k)�z$U�?q�'��Bj����v6�H�O��?��$�xJyi������0N�wz�/1��R|���)�|�s'>�x�?��e�k�y�p)�����J��'����^�[K�����o�R����L�}��w���Fqo-m�3�����:���^�x=����m���=��}��va�3�����G�S���PKQ��;O���9�CTK�������+u����$�^OF�!��7���oyqp�9�� ���$^>��gQ�Z��}���R�����q�U��:�`z."����xki��`���)y��y��g��=��}���;�����l�D���{oj)��)��vgI��:�B�������G��q�+��{������g��]L�������ax���]t�)�n�������G������;�������B�C|ji����G<�S�.:��sT{��=�,���Lht&������61����~�p
M������n�������G��Y@+��z�����m�*u�>�SK��]�S��1���xJ�E��;��&��-���Qgq���^t&��}�SKX/m5>�t�OI���]k��K�I����G��@+3�z�������������6z_��0<�S�.:����������Qgq�����3i����� ����}��������)��v����x����������M���1�����o����u��J)�<�S��,Y�Z�c�u.���Qgq��f1��.M�s�iwQ�.C��)�(N��Z)m�:�����:���^:��������R|�g|����$?��|�(^�^+���y�~��]�W0QK!�#�����b�%��$u��j=��J�U7����@^	QuQ��$�z�y
�75�R|�g|��3-�!|>C:�[`k%:�3:��� ��k���j?����Tq��f1�R}�:nD5��&�-j��je�A�pGw�^Ir�����Z�����~U��;��G���\+Q�Q�����YW?���`���T����)�HM/p�),b��)�u�"���m�1|�B:D�k)>�3>�����>�!E�x[�DgtF���f��M��������#U��X� R���������-�Z�pu��QupQ��$�:�9��j��V�3:���i����E���p!$:����n����]	�A�.�(u�
�����]��#�>�f�f�}P+�O���`����:�,_7VJ�����
��f>�3y����8�3���xJ��)�Z�,�W^+�������R����L�}���8����Z��x����y���-d1��wk)>{�Y;nX��������U��O)�"*��x�I��>��~�R|v�����J>w�3����1d�C��"*�"*�w��k}^-�������Qg���>0(���_x��mu$��<D��.�h��hJ��mYwf�������.u�>�R����L�}��g!bXq/C-�S<�S��,i�T��E��T+�����H������L�}�����,�J)��)��ug���:G}�nd��O������;������OA��)�R��C�>����W��k�rq�bv��dO&�#zvF��b2�����\��C�T�)@S4%���p�G���j%6{�Y���3���;N��&���by��
�!^�zW�/@Q%�6O�s��E���]���L���L���d���0=�p�����.�b)�v�|k}�,�{J%6{�Y���3�����o��\��Kl��h��hJ��%�O�T�v��f�6�s|v���b3I����m��!�;x/��(J�?���V�*����|��z���b2����i������>��)	w��k=�,^�����=�,��r����������]�s$�i���4R %��N�s��C<�D�ePe�(@I��=�G���|B2$Cr�
�m�����OPK��������d�$�z�t�8j;�����vr>�@
�@��K@�v�s������A���L�v����k�V�2(�r�-�m���.���"�'I�i���4u$�(�������cgb��0
�0��K@�v��<�!3��A�5��n�@���]�[+A�A�����Y�6���	�I=I*��y|��$D�q��-�9��Hk%��(��������&��|Ja�a�-�4o�`��x]c�eP��[��f��#j���R$�$����������H:n ������K%��(�����eN��2,��2��!X>�)L������q��x�,�����O)�z�T��W��I[R
����G�}"� ���R
�@
�n���ogg9e���S
��X�����`�|���oWL�<���)�)�v�|k�rI�4����'��W����`�|��l��Y���|[K�H��|;|���,��k�R
��X�~��:X��e�m��ts�I=\V*aFa�t;|���:!-r�-�����S���:X��e�m��b8w�5X)R R����������j),{bY��K,��r��V=���{a|�!f���S
�0
�����vr��hj%${"Y��_4�$[���_�A�G�DAD����Z���{�/�M�l]�,N5?�j�	�$��`zp!�S��V�(��(�v�dkJ���Y�'�j������.u������`|�l��!e�lAD����Z�Sd�6��n�1$�#Y��s2����o���B:���-��(�v�dk���(����=�,��9��	�'[��O�k��XT�a���
�DAD���s��$��m5��=�,��9��	����`|vl�o����DA�\;|�5&9����R	��HG������dr���>���WF�� 
� J�<���������=�,�9��	����`zL�
�Psm�QQr������e��������K�s14����o����6����!Qr�������xG�]�^��4�F���;D����v� 9i�6����8��/���F��c
i
�8��)�au�*���=�Nr��D/���Z��Jh��O�I���
�!�<�+{Xi
�����m	Bo�|���Z�����Tq�e2?S}�:nL5=���W�;QK{X�
��jO&���[���������
�4{� �
��n<+�=�4���~����L�k�6-�>�=,�U1^&C�Q����U�mR
Y��[K{X�
��jO&���kX��\)�a�A4DC4��1��B���sJe+
�z���@��?S��x��S����QU����_��:���U�{�-����������G/�v�0D��<�=�5��h���;�WX�8����a�4@��h��g��e�oO��=,�U�>�O|��:���U�������)�au�*���=��^D/�Lt)�a�A4DC4��1���&^�^+{Xi
�������zo�UE��=,�U��t)u��������}3�/E���TkekR!uR����z�:���OPK{Xk3�]CW� ��I���H�O�]���A�Z���UPU{2��>����jikmF������{KM�u���9%�� ��V������I��A�����N��"��{!����I�3�$�D��=�P�P�'����h�B�����6!�Y���:���hro�G&���ZfzJ{X�
��jO&�����_������Dk��R���q�UO
��t������jikR!uR��t�z'z_E\ji+mF����t/@�y_ ��%���}J{X�
��j&���#,�x�N-�a���8�=3�����o���D	��r|J{X�
��j&���d�+Z��V��@k/SK@�4��������Z�V��2P��K�=�%��z��I�N�'�d&�t4��������B�KR��$����|�=����S��!B�
���_����R��{az@b
Y�����6 R �L'�wv��{u�����<���2��������1=Q�<�k�kikR!uR��$�����^�^K{Xi3-����h2���=��{yW��*��x�J��<'q�Y-�a���84�mz�&�:'��x��2�����R!��;S��z��i�)�a��tx�F4��9���#��xKY-�am@*�@�=�d^{����qA����6#����Rg���sE^��x6�f���8����6��F��K]�QupQ��t�x'�y�~g����|�g|v&����+Du_�]�����#�#���>+�h5+R�G����:H���#����e�����F�n���"����{I���sZ��[J�������x���y�x������<B?����{yq=�J@�����*�P���o���wi�aO���������j�%y�����}��R|�g|��3
�!|�B���JtFgt�A�m����A{�A��=R��6J�B������Sm!��F�Uuq�JDE��E�������}����R|�g|��3-�!|>�:
�V�3:���o����M�sVK!�#���.u�
��GT�����Wq�Z-m�[�s����������#l������3>���i����KkH�JtFgt�A�m���B:�=
�R=����.u�
��#���}3�/E�|
�%zPK�V�_���4C�	D���K��������j)>{��}>���I���H�O���2�|h���F�rQ�����Y�w%:{�Y{��?p�':�w�{j{��Vu�X-ETDET��<y������SK������,��;�����A�5�K���RDETD%���x�}��KO)>;�9j?�R����L���\D
[����F�U8���J�����|��xk)>{�Y�
[����;N��A�VLOE����]�H�K��BS4_S{+����mNaU������Qg���xt&�����<�~\��i54ES4%�b��l����O):{�Y�����d��`{"�M�[����O�����?���e]l����o�R����L�}��� b�C��F?yal_�4E��5�������]����=�,������Eg����>�����]�{2<�S��,i�Z�E�����������^t�8�j���5�^�����vKi�w�����7ex:���Z:I�S���c{���=�,���@k�I��=�=���w��R�x����Y����q�v-Eg�:��"�z�����l�@,a�D
ji#O���xJ�E��t�6�	�Rt���8�'2���I��-1>w�{�3WO���;M�5�Y������C��8�'1���I�o��DAr������n�OI�����i��n)Eg�:�S>0���u��`�	u�8�.���e�I=�<��l�o7��F<��	<���I��\��C� ���3>���?&����3����Z�����#t#����3D����R=�*������:
��FT���K/v�������H�����j�%��������������w������j%:�3:��� �6k��U��PK!�#��l�d1�R}�:nD���.�������3DET_��x}�,~�j%:�3:����:��<�������G�Fw[Y�U���R=���H)u�
��GT�SX�a����R��1q�#�N ���$^>��O)>�3>����>_a���V�3:���o����[J!�#�����9uA�gR������/!��ji����
������kI����:��Q���L[w������������@r}/���1�Z
��@����P�y<�~"wa�o���h�C��*��^����cX:���RzI�����w:�Jl�g�vb��as'6�s���h�H+M�M��T�)��)Iw��kl�������f6kW%�:l��f���G���f	Q����l��k��xJ�E���|d�E)�g�>k;PJ>w�3y��c�9\�x����S<�S��<y���%l��O)>;�y�~v��;�����	�q�g)l��~�R,%�"�_Y��h�����fq�����<6�s��T=���������t���J)��)��tS����}�`�����f������f��oMMO?"�K;����)��)Iw��kn��{�l�{��c�8v}e�z/6�t�>����vy���j�.�b)��s��/2���Z���d��L��EfR���>��Q�Z��h����9r���[�w.�Rl�g�8�geO/6�t�>������C'��F������O���^K����KX7�QJ�����Y���>���q��C�k���<�����tW�9�R,%����;�����,���Ofq���l�^d&���@,O<����xWb)�b))w��k}m��.��]�h�8�ge�O/6�s�>����U=�tW�nM���;K�5��
�A�Z��m'�|�r8l&�������=3]Q*�`)��s����2�$�kK�k�'�&�����������`<{�J�&�ZI�M���;K�5�X����k%6{�Y�7�Y>�<U�]�s����w.���S��c�> ��!�g�I����3k����a����'I��,�:�����A�;n:o��g86m��S���$����-pH:���R�s��&�oK)�)���������}��RX�eXv�2��!X>��ka�V�2(�r�M�m����Ej)�z�T����"$D�q���4q	�w%��(������K���)�eX�e�,����#����T�2(�r�-�m��m����Z���$��:$E���������-���R
�@
�n��o�`9��T�(/�����e��C�|�����R	���������z���"�'I�O��!)��HM��1�M����)���>��1X.�_�+��7j��~Q�y;�G8�}��A�;n9n�5oW���I=I*n*��y@$D�v�T����������Z1���K%��(��������+���\+A���o��e��sFM�V�!_b����Q�Q������x�
��l�
em�\���9P&�:g����N�0�]	�0
�d�����u�G���>����e�����jX&�:������W�]	�0
������1�����JPv�����J(��2����W��l[+aFa�l;|�5E9�mW���JP����W���r(w�m�S�������EkQ<���$����Z`
��cjO���;3�9����R��X���R�}�L�}���_���[+i�(��m���$_!_���=�,�U��?J �\�Q����8�R�Q%��l��^Im��JH�D��%���	�$��`|l��o�=��DA�\;|�5&9E5��JH�D��M���!�\�Q���{7���F�H���`J����~�c��������,����	��\m-��F����za�]I�DA�|�:��L�6��Z��dO$��Bw�tB2���byl!�����`O)M0S�����D�~i��J`���1���	����`|�a��JZ 
������1��"��rWB�'��;�u:!�\����0�c�c�AD����Zc�Wy���W$�`���:��L�}���p��6J%��(��k���#�Ck�JH�3��V��C�s���i����i��:���d���r�����S��=�=�P�P�'�I����5,�Ht-�a�A4DC�'�$E�}yP������=��h�� �6���p&�	ji�cFU�W�xq����:nP5=����h'�jekR!uR��$�zz
��������� �!�f�D_�3t��a�4@��h��gj�!.�������1����l�F�aT7��^j���E�B�������:��d�{}��f?�=�5��h���;�W��>wa���y���@��?QcI�G�)�ay����{���*��SMoz�;���Oi�TAuT��$�z :�,^������ �!�f�D_a��)������=��������
�������<fTU$r���Q����U��S
�����V������I��A���7r�����
��{� �
G�~�����=��������
��������1���g�?p��j����v6�fh_�f�.�|z)�au�*���=�^r��D�W9<�=����>�R��M���k,�
V�����=�S���UPU{2����B\D�kikmF��9JD�B4���#�^�S��x��)�au�*���=��^D�M\��Z��Z���,�!������NI[�Oi�TAuT��$�z ��8<�)�a�MH����JD�B4���C��"������:��d�{�����N��v��\�����PU�
�:�`zd�1�OPK{X�
��j���;;�j���=����>�R��M�}���%��-���Z����TH�T{0��.�V�^��V��@�3�Of�w4�����(N�y�)�am@*�@�=�d^@�vT�)�a����wK��^�&��}�c��Un#����B����I��t��6d�6xZ��s2������?�#G���jikR!uR��$��Z�I��$��,��h=x&�~��7t�k��0= ��+�/�jikR!uR��t�x'�T[����6#����B�M�}��$����yKikR!uR��$�zz�b������fZt2<����o���DqR��u�p:��X�w=�,O���H�hqh����n�&��}��[8W��]-�am@*�@�=�d^@���ki+mB�/q`����n�&���@LG�!�{ykikR!uR��$�z��b������fZtY���L��q�]����~����R���"/�
QuQ��t�x��yS�6�R|�g|����$?��|����V��������"�m������Z	�A�b^s1�'���S���{�����)ETDET����c�|�xi��<�������g��C�|�-i<�JtFgt�A�m��=��[J!�#����b<��$u��j=�}Y��Oi#Q7Y�
Qu|Q��$���y?�M������L�w������V�3:���o��.���S
�I�N��:H���#��U�9$�^�Z��ky���V"*�.���$^>����'xJ�������x���
�8��V�3:���o�o
y�@xJ!�#����R��:|D����(�S��[J��,jBTD_T{/I�|>CJ��������L�w���/���Z�����#�#��Z�{X�xQC-�T��j��:H���U�.l�������-�,>A-���"���8-��}����w��R|���v]g��U��L��.����k����Z��18	���J��������.w)>{�Y;�R����I������5lb��l���;_�<���=�����A�=Dq��S��}�>�R����L��� pI�I>QK��wf��uQ��$���9n��������]���N|&�vz."n!��xS��j��4:x:���Z�w}�������G��_��Gd�������1�V�L�D8�!v���O�t|O��t�w��9�c�v�?���Qgmx�C�Nt&��}���

����R>�sO'��^K����)\����=�,�j��j�Eg����=
���jPKu�7nx��	<����k������=����.u�����������Ha���n-�]��)iw��k����#����Qgq���]�3i����� ��{�b�� �)CS4%�b���1hi�)Eg�:�s�2s�z�����m�*u���� b8�x
����Rr�#��)i��YmE�Rt���8(3��I�o��DW5�����L��S<%����:�7�=���QgqPfP/:�v�>��)��(N�������)��Sx:���Z�v=�|$Y�R��u��d����3i������!ro��6�0�O�����?�/��[J�����W���N|&���@LOB������Z�HTN�"*��x���>�r�-����g�Wl)4�9��\�W�:������!R8��S��1�b�b�:���b:���������R�Fh��A��I���Yl�J|�g|�+A�m���C}����KT��w!����1�|��"^�XK[u�����L��T{1I��Na=D�k)B#4B� 4��!�>�!��^+���y����Y�w�����RPu����TAu��j}NVd-�TL��	�z�:�x���K)B#4B� 4��!��!�?�Z�����ct%��Z�G�w��RPu���E�[AuT�����@�Q|�]K1S1��F��Q��y |)Eh�F����;��g8wm&X��g|��1���f��Uo���z��*^��|`t4�����1������	g�F��-L���M����D�$��+�������z����=@��g|��1���F��"��5��RPu��v�]���<�j���������?�C����Z�vue��O���^K/�wj��p��j%:;�Y|�Z
���I���D���k����TK=�&/�
S1uS��$�zz	��5$�R�v)��.������T��.q�D�l���K�[����kI�u��c�[j)>;�9�Je|��g�n�G����vq�f-�����J��)��
����kT�R��(tT���M��n����%\I�#����������^��I��e�K)B�Z�^
��;N����VZ���1$��������d^|����!��/����g�W����O�L��-��	���O���F?yi$z�:���^�x�}^
����w)>��Y���G�g�oQm�G���o�ji��`��"*����g��[J�����v��T�^|&��}���*�+�m�RW���x:���Z�w=��^�\�����~���>��L�}����8�����F�S���1<��S{-I�.t���[J����������^t�8�j�J]�������kbW7��
�g�Q�����<����v�Dg�:�/O#��z�������C,I��RDETD%���w������k)>��Y}�����$��`=�'n�-�[����FTD%���?�h8���w)>��Y�����$��`}"�X�=
���cp�#�"*����sR�+�Jt��sR�J��Eg�����LD����.DS4ES��<Y������W������g�
�d2��
��zxr��.y��]��c�>(���)���I���M�5���5���^�$1��|�(��������u�����OWR����b�+K�]��d�%�z�t�Pj}C<�{vK)�B)�:�������O�R`f`v3m�!`>��m{�A�A�����?~q�e�DRW��We$���H�Q�q���59��Ji��`���~�4r���:��J)<�3<���v�<�a��Jpgp�A�m����w�RDu(��N�@3	Qu��j{Oc,�S\��PP��h�����,�}+������g��C�|�u�t�����<@3�����{���W/�RDu(�v�]���:z@���|��s������A(�R��t}�|mzcwk�������$f{���E]j%,�2,w�z �6j�^!^�<�X��R�T��7c� ���$wa�o���h������m6������H���	8���K����j��Z	�qVG����ng��sNMon�a��+j%��)��u����8�5������cJ���
�d]���^g�����V��H���;A�5e�
{�^��JXv��v~�.��>X&�vz�7���<����NI���\����>�'������WU���"��]PM<��D�mP+i)��s'���,o���(����eqkw)��>X�8���y[}/,�9�%����R�p
�����r��9�t��<�Rpv��zQ��������95=�p��{��;�R�@
����S���`qZ�eW(�s�?0'	�I��5=�P"�)��}Ji�)��q'���8����Rpv��������3Y�5��'���X��e�0J�>�Z�<�WuT�]
��XV?�|p�L�u
����}S�+�0
�0J�>��|.���R	��PV[�+cz:A��l�u.K]�������C�M�T�L�������4_a[��������Y��	�����Xj��4�,Q%��o�INb�����
e����X�NP&��}��f�x�B��Q�Q�����x�Y���JPv��:���"P&��f��\�������W``
�$�I�)�W8��=�VB�?�7U��a<��L�}���fH��sK%��Q��	����+���g�;e�]}��#x6�<�<t�r��a�|���u�G��5�������>�X���M'�wz��K{������0
��r�4m�t9�{~Kek
�Az�����IA�ms�������2������d6%�z�u��jz�7m��K�+����� ���I�����E�������`�a���8LGuc^lw3&DC����������)�K|�����@��U���mGV���WM/��[����Me�
�uh����3��4L�t?L������uW���K{Xm0
��0������rX�>E-�a�L)���Y�u�����F+2�!���2���X;�,���K�������`�a��8L�%���������0=������wWw��~J{X S��}�w!�"����*���{�G+ji�X�uX��$�:a:i�<�=�6��i��<����X������0=������)�}w@��������"����lwa�E�H��W8O���V��� ����%������J��V��L�b)��~�&�~�/{���f��Q������� ���I�����[��8��������6Ee��_W6����k-�����w>aA����U\��F#��)�Vw��?�a��V�#�j�]�-a|+��=��_L_�^���F��L�������i�oXmOl�Zf����0>�X���M�����o���0�fd�Pz������F��nX�R��W�-a|+��=��_L����7���%Z\B^�����W�Q�kW����S�P�F#�X��AX�G�I���}/cl�cmJ����5�q���~���)���7at�*���=�t_{��tg�T��0��DZ�M��6�@H�}?}��[��R�h����:��h�}��>�%���$#��)��!:�4�O�v��n��7at�*���=�4_H_����F#��)�Vo:�A(�t�O�v���S����F��� ���I����z���0��DZ����2�@H���J����Y�MG:TZ4���UX�U{4�t���>�3.�h��6%��EDB�����n��V���[4���UX�U{4�����C([4�X�i���K�!M���l7QT+��$#�
P�AP�'�����S]������H��\?i���`���H�%O(�h����:��h�}= ����n�cmF�O��wr�P ����>��&�ZhuV7X�UX��N�}���\Nq�q�FkS"�^>�����#���>�4:p�]�
����������-��w�<<TE�IT�7�I����3�,�pV�����/�I��7��e�&HZ��G��������+e�V�7
�.YU��<-���U���[U����.����{Q�GK�*�N����4_'F��E1�1:��L�atIy�~-������MP|;M�>��S�-
�.YU��?-���U���[U�o��/�.�EUTE��	L��a���U�������h�|�0��U��$B#4B�27A��������I�7
�.Y�aV'�����5 �t���l�>�q����UQuU�����0:�2��h���F3�;��%�E�iI�Fh�en���i��J���%A�%��_.5��:AQ�>!�����38eUOTE�9T�7�����}��lQ��h��a4�C�����[���=�������.��Fa�%����UX�UUS�	v�P����?��,�~��L���S1uS����{�z_��(F�4Z���A�c4���G�=}KEU�EQUQ��;W��5���Co�F�4�V��1:��4�����buk��-���}c���rnTE�9T�7�����k_��(F�4Z|�Z��h�o�-�yO�!���4����	;������I�ub�N4B��R���.M��1�v����[,M-����%�&\���s�jo&��������h�F�K�k�����[�{m^0�9�����h��+��TL��"�o��{[�]
�-�FhZ��`���,��l�N����b����I���H�&��Eh�B���_\GhZ��`�cb[�55��3L�TZ�L��Z�U�M�E����� B�����'��%��W����3(��S1uS����z�P�0kQ�v)�z��e@q���~��{%����d���l���Nb����^B/�\o�"�K��{�.��#t���-���N��^���->A�v2�3!1Si����{]�~�Eh�B�w
}�DhZ�wSm�J�i�����4���{%VL��9L����A�U�����Rh�>�����M���l�J�i�����4��^	L�TZ/B�N�[n�5��.�V���K(�����=1�+��U=
�E;�$p$�b*��#tV��nQ��(��qs�P�i�{b�W�VY����G`��"*����.._jQ�v)�z��6��{�3B��yu��2��?k�q+T�v��@UT�@U{3�����^V���(Fc4F�0�h������-�ooI�Fh�en���i��H%k��oV]��^�y[\�	�^Y���n��R��h��8��qb*��a����^B/�8�����Ah�{��J����hI|�g|cV���i��L����FA�!����_8 T�Au��j}���rl��rl������:���f�{=��s�mQ��h��a4��C}�"^�����=���������x���U���P��
�TUm@�;R���E�<�|J��`�$���I�� tN�:���g|��>3�;��W������3>�� sT�N����C|�U��j'q<AP�	j��y�W����(��PUcL%0�;���.��h���F3�;��w����Z��G����v��=�"��P{}�*�RY��HT�A�_Q�HY���}$�����jB�vz�];����������m���,��hQ�vi�xfR
bt�i��?��y�K���|�}�RG����:���b�{]R)0-��}V�l��G>�y��j{,d-��zb���l��TL��"�������Eh�BU���a����(�,\8ES[��c��e�B�IT�7�����sg&Z�=]T%��e�i�6��n^����	5�wyxdTE�9T�7�����r���j�]-.�A�ct���n��i�xW�v���-��1�:6
�"���{���N���������g�����0>�x?}��l9�����F;=��]"�_S'1�^L:���U��5��.�V��.|�#4�����-q\�-��1��q8Q'��K:����'��g�>��`�p>�x��j�O�2��'8�h���TDET/>���S>���tR/:�Hgu�L���8:�w?}��9���)xg���J����Z�|���(B�Z�?�pP�w^��Q�u�H,wG�t��#Z��C�7O��������uh��%�����q�[���L�����a[�h�'�k"��o0Si���/yQC�"�K����poP�i��>����C�}��u�p�w�S1uS�����:��5��.�V?���Aq���~���#Vu�X���Q��;O�5�����W�$:��Y��o���]OmwG�iSEhQ�0Si�35^������Q�v)������=Y���O���b�E�J��!e5��c�>0��1���I������}�F����5�_���lO����-'mI`f`>A�����W:�c��(���T�3��M�/���o���'��g����7d�:��`�v}]�~)Ph��@3�;�W���Z��������v������F1�����,5���:~I�>�����z����<: R�'�L:��sZ6�b�h��@3�;�W:VmV�%���y�)	*o�i�S4��b�CS��AL���K�Z�Y��RG����Bj�Y�y�zI����Z���4�@_�����Z��������v��]y��F1�����N5���:~I�>������j�7g�
�f�����6jQ�h�4��C}���~-	���CLIPy�M��Y���F1��������cj�����;}(�G��/�<��-�{3@�s�L�����-kIxv����=Ax��3}��h��u-j��I&P���]c���
�9�Y<��9
��������������
��J����Z�|k�-	�y���� <G���t'pI{��RK*�*}w��k��)�YkIx��sv5�Qx��������7�iE�B����I�utI�����Ixv������������{�{}3,7D������!h�B����C���N�!nD}����e�����(,�p?}�-Om��skJ�Ji�4\k�7ut�(0��Y��=s3{����>���������j���`!�*M����-O��(<;�Y|���G�g��sPM�=<%v��j��(�R��M�fy1K�|�������'���O�z�~�����o9�P�����v���@�$8;�Y������3m�O�j��\���r�CIGQ��I^��)��u�^��gqZK��C��~2�������_�s��5r\#��)Mw��k}W�������g�j�/�T�4]��_���j��I8�S8��N�u�w�]����F��!���>��}��L���L7>�����`Kv����@J����7	�����g��e����z���5>aX��~��;�r������2a��J�����H[w�����>�������U��g5�����x^q3�1O�]�=�����{��Zc{���� ������N���vqM��0�`�a�[n�����9m�����Fm0
��0������N��%�����dJY��.�p�=�$����t?����gZ4��V`V{6�����.���F#�6��i�fx�sN�xh��0�`�a��L������k�o4��RV��/�����:ne5=$g=��i(���X�uX����z`zO�"�����
�a������~��'<��
�az����p<Y�;-��o4��RV���DVd������nIl�-at�*���=��_HoE\���FL�4L3�;����~��0
�#MRP�;M�^��Om��E#�)eU��_��:���VV�C��-]����F#�`�A`�g�����]\��F#�6��i�fx��k�n����t0
�#MSP�;��w��8�����j�DVd��UO��wC�Zt������h����:��lz���3�\�o��mJ���������4�7��g��)o�n��0>�X���M�����t�0��DZ���|oH�~�j{`���M�����V`����~��>Ny��F#��)����A���4�7����eQ�d�F�
���j�&����z$w�Fm32��?���8L��n��Z^�O��F�
���j�&���w��o��zeZ|���������U�U�������3�]$�E#�X��AX�G�I���+���`�q6%����'�q���~���n����N'�h����:��h�{��>S�E�[4�X�i�.�/�-i��{Vm�RT+�st�d����:��d�|������F�h��6%����'�q���~���(j����BfBVa��;[��E�Z��0��DZ�Ah��@H�}?}�
������*��}����Ho��t�FkS"�^E�qQ �w_m%W�u�f�n�8�~�$<��VauV��t�}'G�T�w�FkS"�^C�q
Q ���$��(��^n�$#�
P�AP�'��������'n�'�"�^A�qQ �i��>��&��gqD�h����:��h�}= }�G�<�#mJ����6.
D4�����BQ����o�F�
���j�&���EG���S�w������!M���l�P�8���-at�*���=�t_H���C-a�M��z��nq��l�����wQ�/��t�o�[����m�GK�*�N����N���F��O��b4Fct���&=���K����[�1�G����������C��~����U������MX����e��.���'j��c���GK�*�N����4_F��>���o�1�c���F/i�����h���Qf'��}���Z)�c}����U������yX����e����3�C<�E;�J�����	T�DU{3i�>���s�[�1�c���F/I�-������NP}�M���oV]�*.Y�AX��	�����g��##[UQUcL(0�;���%^c�����h&}�0zI��b-������NP};M��i��I��U�����p,3�����e����+-��|��~�l����:���b�{}�����h���F3�;�����:_����=������o������:�V���`�aV�BY�F��m�����=�E4�E{�H��i;���������No�&>A�b�K���9j��M�����}$�T]�\[���,!CUT��b�����{�E1����GX��h����`{���"�Z�}�c�QUi��{��%�����h�#�A�c4�7���J����-�i>a�����IC�9T�7�����[&�Fh�B�F<A�#4�7�������lB�2����J����Z}��*��h�F�a
bt�7_u�p���;'�t��-���
;'0S����O]���
-n��A�#4�����t���.�������r0Si��;���{u;u�&�z��U�q���~��;&�t��1X-��TV�a*��z�7o���
�E������'��a���~���%*��x@�v25���OS1uS����z����qW��q�3������#4��cOL�J<K������3�\HL�TZ/B���f�UN�"�K��{���#t���-���N����!���h'S7u�pKb*�N`���NZ��B�,.�oQ�v)�z����Bq���~�����X�-6�'�k������t^|���E���E����mB_�VBh:�wSmwJ�&+���F�<�~�k�ZS1uS����z�T���(B�Z�I��&�8B�z?��x��"�<[z�;[oyw����:���^�y=���@��S�S�E���8B�y?��x��Q��J���c�c�:���b�z=�l�Y�5��.�V�:-��=[�]���������\��WO��c\*��b�������N.���M��hQ�Fh�� ���C�}�M<��%���y�Y	Jo�o����{����U������LX����E�v�pN����D{��S7K�$������I�ub���'����1�f�w����Z�nI�Fh�en�����Y��%�N�Y`u	�zs�iqs<�z�u��j{B���[\����:�z,�����:���b�|���Bo��Bh&|��NY|'������<�����73m��U���;��������:nM�=2��Rw2=�^�CE5s�N�����^F/�~����10�1���h{���n��	-������MP|{}3sR7�(��dU|��UX�������S����'��1vyx����c�jo&����k��[�1�c���F��WN�$B#4B�27A����L���V���*�'��_(���x�������3���a�F;uf�N`*�:���K��]�klQ�v(�zT����*M��o����Vg�Z��c��UQ�����[����:�(F�4Z�y/�6S����W������K��F�<FQ��,����s�j/&������m~��Ph�<��!4���������W�?Q��a*��zgj��B�E=���"�?�/��D�0B�z����)�+�Z������q�*�N�����^F/������h�F�U����i�TU�,�k�����{Q��|��c���NaL��T{1�����>���(B�Z�\��2B���z7�zO��.m�>�q���.��!���{I���s>���[�����q�{�i��>����)�g�e�.CTD����xM}.�l��DK��C��_�5��at��~��{$�"�ZV�����J���������j���^�sq�O�i��>��������[QQi��4^k��]��F������A���9p���i�\�o����C��%j��2���TL��T{1�t���>wy�Y�"�K����.��#4�����xoD9�;.k��c��]?+3	�:���b�z=}�/rZ�]
��tqsP�i��>���5u�������S<����o�yo�����t3>��g���/\����]������i[���D;=�&�9�0S�0�^L:���M]��D�����0����8B�z?}�=�~������[7'�#���{I�����.\jQ|v��zW���?��'+���+x�z�R�����d��qh�LN���K'mwv�����iIpgpv����9��|�|js�-	���LDPv{}3�T�����Tw���b�&�bb�OS����;�K�vM�����L}Y�p:>��X�w]���5+]���38��#-i��8_��B��gp�&"(����{�WukE�b�;S���o���1����VT�+�7����S8�S��L���%�������g&w���J����%���y���n�o���-��f1�������TL������b�o^�|Pg��K��O�h���gx��<3�;�W�WmiDK�3<����^��=��~�fQ�����!��U�Qu��jz�yI�"����N���S���|����gpg�83�;�wZ��f-	���LDPv�M�.��lQDu'�89_������6 �n�X�����wWn��S8�?{0����8���5	��p���_��g��{N�2�7m2�%�N���;M�5>x7�M\K�����g�d�o���]��v��e��U�*}w��k�s�u���g����(8�v���MY<l�I�)��)]w��k}
Y��%���E4��9
�t��� J���w���o��J����|-�2���g�<�3�5�Qx�w�=����� �|}p�u}0���L����i^Om�iKB�;��_�O���L���#��q���<hI^��)��s'����������fw4����aC3=��0��P����?�N#�T��	�`:��T�s���zdM��C��#4j���L�����=du�n��J��cP
��\`��0/�����fw4���n�	C3-��0�������5�il��N�����=!� �{� �?�Y���O����'N��X5���a����_��^���J���`p:��X:��3���b����E��!��?�+~��L���#���P6u�CM2y�pJ����Z���e�~��g�<��.�	�3m��0��P��i��ip
�������mi�������C��+~����3m�9��[ rN��n��}�Cm�Ni���_S���m��1o4mN��L���L�@<���<I�$�)���=�4]{�s�.�~�'����5����`
Z��KF'@�3���]��;}7,7B�{Z���o4��V`V{6�4�����.�Xz�FL�4L�M��=�yM�%n�j��
�az����s�����y���VZ��F"S�*�}Y����3�V��������}���� ���I������-�x�FL�4L3<����<a�A4DB����
��r�|�o��l�!2���N��Vl������nie}���� ���I����iO�F#�6��i�f�w����%��FL�� L���)���.G:/����0D��U\�]����3�V�3$�y�K|������ ���I�����+�k4�h�i��i���a:�T���'a��4L����(�r�e����CdJ[�s�k[�u��jz����C�o4��V`V{6i����-�$#�5�i�fx���^u���0�`�a��L�u9SQgy�������d��?�dk�����;}7��E��N�ZZ[4��V`V{6�4������J�d��6%��%Q���[i�oVm����-�X{���� ���I����Y��-a�M�����a:������p�����z+�+�w��k�tQ&l��mJ�����v��4�7��;�o�v�����X��S�_k��U��5a���tz5�q���F�X�,���h����:��l�}0]��L�F#��)��� L�a:p�U��j~��*��eqWq�F�
���j����;9����G�:FZ�M�~��,H�}��j���Z����0:`Va�M���o���"���U>�t�O�v;E�R�Q`BTA��;[��&:����0��DZ�E[� i���`�����"�Z�F��� ���I���tV7e�h��6%��=B�{�!M���#1�Dq%����0:`Va�M��������9�D��}� ���gT�I�����0�Bq��N�h����:��h:i��#}�H_ �i�*��UD����~��[(��\���-at�*���=�t_H�:�;H{EZ���G��4��=���(��7qR�E#�X��AX�G��k����"/K��cmJ��+�2WB�����n�8�r�E[4���UX�U{4���>�����	�D���C+�"����Gb�����*	O4���UX�U{4i��H������0��DZ�zh��z�������n^��-�N�-����������QU�P��L'������~Wa4Fc�C4����)��'Z�1�G�������=����f��%�����������q���E��&���&��VUmITE�	T�7�����3����-����1�f�w��t��!�-������NP}{M��i=���oX]��^9�Z\9�^a��Z��#�����������$QU'P��L����T��G[�1�c���F/�*�0-������NP}�M��z�C��KX�W�5��:A]�=6�J����E;}1�U�j>ITE�	T�7������W<�(Fc4F�0�i�1�^�u���Z�1�G����������.u��e��%��g�~a�X��u��j{f���"O��h�����_�'���:���f�}}�����-����1�f�w��Ty�o�b4Fc�(�T�N�������x���V�jX��/�U��'����}-:��g:�K�@\��^)�eZK�*�N����^���F�i��i��h�F��fk��M���l���S���������-���s�jo&����g:N�x��h�F��`j��M����O�I��l�^o�dU7TE�9T�7�����mQ��x���h�#�A�c4�7���g"W�0l�^o��5d-���:���f�|}}�B�'����D�j��M����H�%�`�������[UQuU����:1�W;�(F�4Z\,^�����W�8�k������_��(�	�������{��H��W��(B�Z���:����O�v���V���h��eVGKb*�N`����^{��Tv���Eh�B���o\
GhZ��`�gbQW��h/S�=�=�:���b�z=�/Y�Eh�B���� B�����X��(�8Q�����q:$�b*���z��.���Sh�J��+��M���#1�+��C�+���L��Uc7��0uS����z�����Eh�B�7m�,G���W+}5���a��m=�'h�N_�U�!��CS�0�^L'�wv����(B�Z�Uh�V�8B�z?}��9���[����[�0Si��O��]�K�F�����B7
���������R)�\F�2����J�����
]��!���(B�Z�Mh�6�8B�z?}�����"�h����0Si����[<��E����z���MBq���~�������������L-��C��TL��T{1i�>�7��(B�Z�Gh��G(#�l�wQ�/C����,_��D{�u�Xf��N����Nz��F���;Fc4FG1�h����.iO�jI�Fh�en����/wZVq}��V����h��h�WX�-����s:/Q��5>�m-���:���f�}}]Vq�C�b4Fct�����;�T��Dh�F�Q�&(��&}s��8;�f��%��-���-����q���Y99m��uZ�Wi���1&������������h&}�0�Ng���jI�Fh�en���o��R/e|���V�}�V`������v�(ZUQUcL(0�;���!���Q��h��a4��C}�[;�	�3>��(3��N���Ly���Y`u	���K
+�NPUm�I�D�S�5��1�����:���f�}}�.���-����1�f�w��t��:��Dh�F�Q�&(��&}�T�x���V���P��
���j�=�N�
�k����.�r���E}�Vx���s�j/���;���.�Eh�B������i��M�>#}�=�N����;�"���{I������7hO��,�#�At�3}��G�=%gI�x���d�::����b����I��!��������C��-��R�FhZo����S|w����><PU'P��Lz���"
�����C��� F�1��u�DN�%/p��N�	�:�pc*�Na����^B�����Q�v(�8�T�F���W�"�k��z�����X���������9L��I��\���B�(B�Z�gz��-�M��n��N�|�3�-�i�uc���t^|�g�����g�>���\��g��`�S�XeQk��c��[S1uS����zz_���-��.���?A�#4�����'q����eQ��;O���y�O�Y9a�����?��������Gb�CbI�)��8:����J�E�����9�Eh�B���G���W[�Us���{$���g��h�y������:���^:��S�����kj����tpwP�i��>���[���E{=��0S�����+�1�Q�v)�z���Aq���~��{$r��8 [��c�@�$�b����I�u!��7���g�>�G,��g:��`�G�:y��A[���`��b*���K�E=��E����zs���Aq���~����(�x�k��y���<+�;C�9D�������K����0��#��
3_�.����<Y�]�]�����l�O���y�U��g��H��T{0�����>��2�^7e4@�71i��@����hIx�gxbJ���	��y��=��,�:TU�(�4�(U}�:nM5�\�zK��y��p8N���K��WqqK�38��{����;�w��$8�380A��7���S�e1��������������q+���7%�����k�@=���o�t
N������Y}��w�=�fh�f_V��=�|�S�-	���LCPu{M���������Tw�j�� �b�������v��uKv�	�_K����)8������c��CkIpgpv�3��C�|�|i��$8�380A����,W�6qL�YLug�x�����0uS�������td��]Kv�>��F@�T{.i�>x�e�wx�gx��3�C�|�C���%���y��
o�)^����T���P�����K�F���������[�bV��F�<6P���K/�wr��[+�-	�yo��Ax��3}��p����@��I�������pJ������e_��(<��Y;��	�s�i��?���7��������KE���'R� �L���U�E�!��o��(@�y��^R^��[�}�#Pi����/�����iIp���%U��g�n�-����5	�p
�t�i���~���M��E��������(<n����^����8���`
�`J��[]wn��[�VQ����fm�	Bs����>���m�<h�N/	9�L�������U���IhvG�z#����ah��~���J:6���d/����)����=�4]s���qt�(<;�Y[���9
���O�v_ZN����j��c����@�T{.���<�����$8;�Y������08�v?�Hl���|7����p
�t�y��5���i�&��!���?����9p��8��N�����/����k�1���)��u����nQ��O�H�z����?ah��~����q&W�����,
T@����ox���v{��?�Y������0<�w?}�M����E�@T@��N�w�7Q��g�8�W�\\�g���`����c�����8�S�.8��T�*/2[{-2��|���7W�������Gb��"y������+���N����_���efK�ef�qz�����<A�{���������.���e�+z�#e�MxKF�
���j����;9�w��6��F#�7�j�����i{���JV���0�������=�*(��J���mg+�l�A2���]���]���W�q���.�uO����{�F�B�0���I����x��0����������k������Fo@
��@�������L������W6� �RW������xt���������z)��h���:��p��=@��r�S�'S�@
���f*x���]q���Fo@
��@�������.[:��f#�)uU���?W��C�:nq5=_r��R�7}-a�@+�C�=��`Po���T�Mj��5S�C@����w�>��
��z����Tp@[���&^��f#�)uo5�AtE�9��������E{��F#�h��ah�����R��-a�5P5S�#A��t��-a�5P���
hk)i[��l�A2���������P��+�nO����}&���;Y�<-a�@+�C�=�^Z��P_�8������P�+��?P��C�j{����,.m�#Z�uZ���;�Z���E#��)�V_��?�3���!h�=��N��O/�L/@+���g���P����O2�h��i��/|�0M����t���=��X�uX�����`Z^������P��W�@	jp���b��,�Z���v`�c!�$��i��mJ�����~��4
X�U�i����n�����S-a|+��=�N���L�����E#��)�?��wQ�4�7���+�t��^�
��J����Z#}�b�n��mJ�������b�����n��Zn:��+������L����-a�M��x�R
�t$����>���������6��X����_k��C����F��L�7
n
�4������v�r�S�-a|+��=��_L_E<��E#��)�V/,*\X����W����N����3e���h����:��l:��S3}�E�i�E#��)�V/+*\V�i���`���L�"������0���I���tY���F��L�}a
���`��Xq�����h����:��l�]0��m-a�M��zQQ���PL�?}��g��c��h����:��l�=0�����h��6!������20M�5��vc���W~�V����0���I������pj��mJ�����V�{������_�'��c1�Yqm��a�v���f�������S�j����;�������@�Q�Fi������K�+����o�Z�Q������v���H[�f)~e��%����Vh����_�y����h����.~7[Wqu
W����:QZ]���(��(Ei��P:�C����(��(=���������-@���V���3M5��:Ee��W����B5����r����$������I�������/�?�4J��G6�������!��(��(=����������R��B�KZ��OZ�u��j{����K�����^����$������I������..JjQ�Fi���4��c(�SQ�t��4J��8s��^��w:W��_YhuI�x����[��u$Z������_i���l�N����
-���:���j�=(}�r�w_�(J�4JGQ���1���8�=-��(����QP{M�n���S�e��%����5���W*�&����h�I�|O�Uh�N�]�C�Uq�*�N����^���J��v����?�Y|�D�@:�z�'���|��{��<[��lB9��d'���$���I�������6�'��.���A��4���G�=��H�:([������<����U\�U{5i�>�>�[9[�]*-���pb3J�~#�j{F���]v�F{�*���ZWqu
W����:QZ=I�EQ���Yd�Q:�����;)*�����E;��U^Q��*�N�����_J��Ei�J��� JR:p�U����`�����F�����F���UQuU��t�}'7�^������h�#����F�|��j���<��[��T�Y^K�QU'Q��L����GZy��F1���������#M���lwQ��*�I5��1�C���J����1�����>�?�D�@B�{?�t����[~�V��~�|]��u��:���f�{��>�
��Dh�B��}�pf����7�z��V���j��[4�����aT�EU{3������h-��.�V���F���
�j�5��C1�9q�k����%�z�*�N����N���F_�[���_������C_x����W�zw�������T]�d-���:���f�|=����5��.�V���9�h���`�sb+��5�KU��DUT��b�og'6q��E1����}C���"M�������E4�E{�*�I[x�����jo&�����*���(F{4zU�Z�k(��4��E1�=��7c�o��6u���DUT�BU{3i�����g�>����}�Z�I}�zu��2��@kZ��Z��c��s<�N����NZ��F�+��h���FMz�������h���Qf'��}����i]���_Y`u�z��n���`���n^���[�Z��WSe�%qW�p�^M����jL��4J�t���C�%�E���Q�Fi�g����i���\.�f�_YhuI�z��6+B�H��[YmO�Y�R���Z��cd���$qW�p�^M���s*�hL��4J�t���B�������=����?~�������Vm�	B+�NQXm���<���-����������S�j�&�����-��kQ�Fi���4��C(�L�jk�Z�1������������#���V����kZ�u��j{�zN�&�������K���j�:���j�}(�����Ei�F�(J3�;��%��-������PP~;N�.���7�.i��jZ���VM�'��c�>�~�
�����5BE=I�`*�Nb���^���B�����k��d������hzoUmO���%Bo��c����|����S�jo&�����&7��h�F��dk�M����,�EV7U��7�i>A�j�?}5QUGQ��L����So�g�9_�����{���@F�|�n!~v�-�b�������}UgQ��L�����{�����7��D�@B�{���xV�^�"��W�U�NvN��,���I��a���}�-����� F2:p�U�
���`�sbI��{[��c\�7�BUT�DU{3�4���~�/�sK���0�GFk�� F2�������(���������QU'Q��L����.��h�F���o\�h���`�sbIeMh�>�q��	7�	�:���b�{���i�����G����5���|��~��{&��y[�����QUi��[�oq�E�b�K����6��d4�����5�L����h���JxTEU�/F�v�q��9�(F�4Z�eh���HFn�*5��C1�7q�G�qo�"*������}>���(B;Z�_�W� 4�����;&.��^�Zo�w�UQu
U�������%���E1�����B�E2����X����whW�whE�	�p&��b����^B��2�(B;Z�[h�n�HB�z?}�=�~o�����C5��TL��T{1i��>/}���k^� ���,�s�P$�i��>��~�{�j�N��'�N���YL����A�"O��^3�����;���e���y����{��!�Z����������
���jO���;9�k:�eN�z��C4DC����E�]�.��kI�_s��A�~�C��	jo�|+k�R{�_Y���)]U����={�:���VU�}�%]���%�<��
�7h�:	��\�z]�|�*�5	���xf�w����?�9h�fhb*���i��*�7����;U�;�w�;�Q����VT�qJ*�v�AK�bA�br��NB�=��^@�%m�x�b�B4DCt�����;]���-	�
��LKP{{M�^�{��,�:tU;��	�*��PUmO�\R���-�4H��s	L�tI�����gx��<3�;�w�Ny��dU/<���N���q��E1�������;1u S�-�����_�	�O��D�z�9����sI�u�sQ��(�n�gpggZ���q�S��������<�du��o���j��WU����������j�=�N���t�����g�E;=���,��T'A��L/�wv��E&�F!�!��� D�!����#���,X�N{��cU��+������I��A�Vd�k����?A��C4�����7X�P�>�}���7�B����I�����{K[��-�~�������H�>�l]DZ��cd���AT�@��LZ��w����hD�����E���F�Z��7���h/8�TA����!�\���-
����� D�!:p�U�	��nXn�(�������4�"~+w8��98���I���rj�-	��p�|�Ap��3]��0�"Q���������x�pJ���,�)����g�.�����L���L7F<
V��`
�`J����Z�Y��e�o���,n9�Ax��3]����q�w��Q�_LH��AH������WR������@�7�|��h����'�z+D���oj��c����TH��T{0��.���h(��h���^tpUP �w^m�V�u�P,�C��u��E;�Z������s���N����S|��gw<�W\�g���`{�ON�z�p�2���J�������[�s�E�!��A�����n����"�-��18�R!�����2�5
��V�����t^���n��E����h��w\���J���/���-
���>�;;N�
4���������kykR!R��u^k��[���Q�������zH�����/,����6:!�_<�4�wQ�/#^��I<��%#�h���h���I���3��m�o4���j���o�I���z]S�Lx�FTC�@T�����E��?~�%�,����0P�V�C�4�Ca=;n}5�G�=��h
O2��Vh�V{8i�>���+��h��P
�L	�E���]<H��FqP
�Q���������%u����0L��U�j�4�j_=�:ny5=dg[�%^�F#�p��p���&��j���7a�A5TC5S�cQ��t]�:�7a�A5TD�����k.���E���������kav��jz
���U|��F#�p��p���.������v:��0�����I���^��o����0������L
��5��AS<��W8�@�RX����7T!�`��[_M�i��T.q��E#�p��p���.���5]��x�FTC5T3)<����K�8y�FTC�@T���I�x��{=��N
�
G(S
�m�y�����j�=�N���t��W:5Z2��Vh�V{8�4�����ukO�F#��)����A��E5-��i�u���*����E#�p��p������+�x2O�FqSR->���Y'�������+]���[4�WpW{:��>�>�?v[2�x�j�b��XP����D�`.�x\�F#�
��jO'-���-��h�7#�����A��E5=8���g�w_��h�1��:��t��}P��?�7a�MI���|���PM�pU� ��t`���H�x���0B�Z��N'-xj���e�$��0���Z�sz�@j:����n�8�q�O��F�B�@���I����K�Z4�x�j����{��AM��l7\ig�d����:��l����>��6��0���Z�e��:�4�O�v����t`h�V:������K�j4�x�j�N��;��AM��l7Z�����0B�Z��N:�=������d��6%���Fc:p��x�\���t����Mn�5a�@+�D�=�N��Po�*bn��mJ��K�..5
5�c�L�Yl�>���F�B�@���I�����;p�FoSB�^htq�Q0����>��F�Zl7q5p�F!�
��j'�����E#��)�V�3���(�t�O�v���uG\�F!�
��j'����;�c���V�2���(�t�O�v����[�l��F�B�@���I��zO9�w%�h��6%��EF��EFN�X�u��2��CwR;�����e�b�kI\��I\�W�I��\���+2��8��8��/�I�v�tN�&�T�(N�4N�4OA�U��#�����?axu��z��mq�'�z�u��j���N�!����d]U��Y�uY����pz�~y�8��8�i&��p:�m�nQ��i�i��
�m"�N�)n^��W���W�a�!����������+]�x}���u���5���:���n��}8������i���8N3<��9���t��4N��H3T�N���H�8W�f��%��\�Wp����;y�R4�h/Y���ZY�uY���{p�������8��N��O;p:�E���Eq�qz��
*p'Z�R�����+�.y�~C>Ax��I�����W*�z���%�q���$�"�$���I����Vu�Y��4N�t����%�����Eq�qz��
*p���%�E<�W^]������^�Rq�t{��>�C�����M�n�^�r��"��?�������SQo�lQ�v�����	�t(�i�?�Ms����%n7n�^��"�>T�Yg���M���tn�]F-��.��q:��4������#�E|����d�eYwdE�id�w����M]��8��im��	�t(�i�Q7�P��h�^�n���xGVd�EV{7i���SQg*Z�=:-���q:��4���,�tgqp�v��Rn���������j�&
���G:��1Z�]:-.&�A��t��n3����z�R�MTo������35���:���j:���+����5��.���?A��4��c�L�Vl�ZEW[����.�WkI\��I\�W��k����M���Q�v��z�|���XJ�~?}�}[�����E��*��oI\��I\�W���A�R��-��.����� J�R����)�{**�����E{�S[�wj��puW�����+��]������K���
�R����)��)�t��B���U��Y��Y\��U{5i��J�iS���(J�TZ����kP��+��-���N��~�5�M\����f�,�*�qWgq�^M'�wj�����S�(J�TZ���pCQ,�i��f��-���B-�kV��g�����i\�W���Ai������(�C����
��R�������X�u��
-�kV!�+�2+�puW�����+}���&�(J�TZ����Q���U�]o���i{��fyV��WquW����zPz9��yj�*��J�	���Z�j��m9�3j����)�*������j�&��^�=���>�EQ������4P��Qz��+~)�`��x��z���h�/���_��DVd�DV{7��_�V�)Z�q��8�8���N�����'��(��#�SP�����~li7)�'�.y/��Ax��Ij����gnW�W�}�����|������j�&����!���Q�Fi���4S�C(�KZ���(J�4J�3GA��4|,k*����	��C\�7��^���X��[Z��@[�u�K�u����
=A\��I\�W��A�-�[���FQ�Q:��L�t.i[���Z�Q������v���|U/�	�_apu���zt����c�:ni�=`rM�!���^��'�<A\��I\�W��C����8�(��(Ei&��PzI�����4J��8s��^���f�������!��q�O\�u��j{���V��7�����o�ZY�uY����p�8�}x-��8��q�fx�s�g*�(N�4N�4SA�4
|�3m�����Wm)�Wp�K�U��	v�`���������5�����/�*�"�8���������k?�7��.����{�8�i�p�\����T��z�E�<F9.q�Y
�*�N�����_J�;�r��p(����A��4�����[�\]oq��E{}97y^ac^Y����M����]<��Eq����
�����4
8����7�Z�k��o���3��fdE�id�w����r�+ Z�=:����/���4���n1����n�n��/�ZY�uY����p�^e�k�]:-.$�A��t��n0��^�toE����k�Z��c��UQ�����7���X?�����?1Z[B�1:��4��a3�U��sgZ��Ws�gV\��i\�W���A���PkQ�v��z�|���XJ�~?}�=������^_�]>Ur�TI\��U{5i���'~{��b���>�'������~���)����n���>�����7���,���I��`����^��}1�GF�
e.�e4�����E����U��^�n�����,���I��`���o��NS��#��;�2w�2:p��r�\���rE.i������T�NQ�DUT�DU{3�4���~^���y�Q�vh�zQ�6�XF�|?��rE���x{������t\�U�/J�^�[<�����K������(M��������.��a�v�S8�o�DUT�DU{3��.���!����h���D����~��;(*��&�Z��T-���lGUT�EU{3i��F�KR�~����U�{h���XF�|?}��2���7�KUuYa�N����4_s������F1�����C���C=a�]�����W�����E{}9�#90Vga�M'�wr�s�E<��EA�A:�_P�&m�t���n�kQ�i�f����������k[2��V���o�&ob�_[����we������vz��.&c-����=��_D�E>��F!�!:�L�A�3�[Td�(DC4D25A��5���t���	#�;Y���W���������U�+��U����^_N��H����YX�G����[�����DC4D;4�mOt����c�EA�Az��	�o���j�]����V��j�LO[�u��j}z����	�����5��:��`�}=���i'x�Q��h�A4��C��t_��o�B4DC� �N��e9�ui���'���d�?�AdE�9�����E=:�E{}9�=OVauV�����@���C��n���4H��?5i��H?�x����h���q�'���&~���W��
c�C[��� �b��)�mO���}(�f�I�Ns
e��� R'!�L/�wn�s��%���Q�vG������HD�z��FZ��{,��95��1�[\Ev�*�N��=��^D�+}[��->@
t$�i�?x�����F�r���h���K�J�@T'A��LZ�����7�Q�vG�6Y�!:������)oy{[�vBU�j�?|1AT�A��Lz�����dz���hq����HD�{�n�x���9l�8��J����Z}��\�B�;����5����z�������%rI�xJ��������
�s�jO���;9�%����(D;$Z���7,D�{�j�]�J)n���N���UcOPuP�������ni�����h�D���o��h:��`�Y�����eko��c����7��C�,���I�5z=R�b�mQ�v����:�t�O�t��Sdw�������H��YH���kt;/R>Q�F���
@7������^�V����n��ysV���l��YP�'��kN�s^�\z{u^����EB_8|�i�
�)5��c1�&q�����h��.��TH���?�.�y�-
���V/��D(�t�O�v[-���$�E{M$�MTA����[���[�k��^#��SA!��U��lw:�8Y��}��n� uR����z���>�!��BW�������d��U�w�U��x��UP�U{2i��.���a��D��huM���5���hz��`���N�����[4��TH��T{0i��/ekIx��
quH7����v���������� ]Fo����;��!���KZnu��ec�h��!h���I�����e9���
����#'����J���	�g
�C�������N3�g���f���S����������>e���P�%�~ec�h��!h�����}Qw��l���@
�L��y�s��C��c
�az����$p<Y�;���x�1�����W��QdF�q+��:%�]���1��B����I�uY�z�����
������J����h���0=��`8�����U�N���cJY�}�5���:~e5>O�N���l����:��p��]@]�����fc�6�j�fx���]��7c��4L�����'�}��������cJY�e�5���:~e5>^�N�z���1:�X����M���KN20O4�X�i��i&�~��k���6�Fc�5���!�~��~>y�H��MA���cJY�3�jY����������	v�fh��^�1��P�<�l����:��pzi��C}%�$��1��dZ\�]�0�i���`{����C�����
�C�j'
��[:ou��ec��)�� PG����#��3Y{�z���[6���Vh�V{8��.�>�Y�i���1���Z|��(P����1����[�?l��Z�uZ�����zIE�q�W6�h��C��� PG�����W��������
�C�j'�	�����i��mJ������W5�O��;�{��5�f���������1:�X����M'
xj����U|Y�fc��)�V��?_���_���n��W*E�����
�C�j�&���wZ��7c�M�����g/�4��9��,Z�Uam��X�uX����:`�N�zI���1��dZ�k��0M���l7W�[��������
�C�j�&���9�8��l��6%����/����_���n�X�tgu�o���
�C�j�&����|�K�o6�X��i����k������W[�Us���+���M�W�Fc�
X��!X�G�I�����TNqq���1��dZ=r����0L�~?}�m��U<]����
�C�j�&�����M�z���1��dZ=�����0L�~?}�M��.uS���1:�X����M��9���}�xy�1F��H��\Si���`���Zy\���cVauV�����#��f�6u��ec���>��N.(
�4�����/��ri�o4���UX�U{4i��H�eM�8G�+c�M��z9�iq9���P���wQ�/C�'���W��h��}p����s�jo���;��9�~5g��4J�t���&M�^�sK�x���h���Q�'(�}���H��=��U���wr~��X��q���V��Z���d��N�Y'������I�u����MD��b4Fct�����sKy����b4Fc�(�T�>����E������SV���O���a�'���U�������^������cJ�i�1���t��1�o�Q�c(���J�k��Mo�1�G����v��=��k$�QXu��x\G
�*�_Vm��<��/�z�7��w}x����s�j�&����G:o�7��(��1�f�w�+���=�(Fc4F�2CA��6�{����7
�NYU�����gV�-��'��w:�����~����tpuW�����P:�5����,J�4J�P���h������vW��h���Qf((��f\s:Vq�o���SV�?`jV�����+���	v�fh��^_�5��x����U\��S
�L�N�t9����%������D� B�|?��#������FQUQ��;W��5��6�-7~���h����g�a4�����G���]���f�<���*��}Q�)}���Y�v���5��A����@��-�x���eVWq��;W�5V:�+�w-�Y����%RU�(Di�������s�~�,��*��~�j��Jg�
��Ei�J��k�����W�B�k���.�=���B��b*�b*���6������:o��
-��� B�����������[<���y���*��{1�7_���[]�����h����k��M����wP��dq����� \���J�������^<�f1����z��� F�|?}���Lk��o��h������z����\{�(B;Z�$����(B�z]}��{�wy�X�b*�b*�w��k+�����7�Q�v*�z���]CQq�������N��=Jq�����b*��������Yn�5��N�Vo��i(����O��f���x�������@TD����?}^SVonkQ�v*�z���
CQ���~��{%�5�E�o���}h����s�j/&��^�=��6��F�����B7E�������������^DET:�d����-��o�}
}��	��'Eh:���C�G(i������{�}����^�������5�Q�v*�z��mq�PF��Z��n^F�����������!h��"����;��������}j�S�(2#32{��T���e>������(2#32��}��v�~8R�X|�`�
S�z�/����`:n1��\���v�Q,�R,�<e�d�2�W�oq�B�"32#�g���B�cO���f{�����{����i2wO����7
��0Uo|���'`:
��S�sm���U\���X��X�y����1d�R����-������ef2w��=��kQdFfd�=�@��7��i�@�Q0u��vB�S0����x]���O��b)�b��)&s���Y���f{������Yf&s������O��"32#s��jn��{&q@�$���T�OJ�t�Zj{
yNwW�(�b)�z�0`*7�����r�C{�7������ef*7�����He���(2#32����v��UH���SW�j�M� ���1�WL�������}�����/���7��X��������Z�;�}�|y���Jfm��D� 2�r?�c�;��M��Q,�R,����rm����Z�{!Z�]���|��DfZ���`z^M�����|�X��XJ������\R��Qdv%��5��Ad����{�i_{�Q,�R,����rMe>��kK&Z�=�\D�j���L�u����k�M�7����>8�?�
TE�T�7�����;Y��mQ�vj����1:� �|�����~���p���{�QL�TL�����;��{:D��$>;�Y[���9��t��=1�q�[���b*�b*�w��k+��nq��Eh�B����W�"4���0�Q�hi����fQUQ��;S��6Z�zc�S��?[� 6��������(KZo��7���������������U�i~��Th����u=Q�����8dS�����ZS1Si�3�^[��tqIo�"�S����
wE��[���t�u�^X������{��(�b*��z�j��Z�;���6��"�S�������B�z}�j{���N���'��#�>����W��k����U�z�Q\v��z+P�V�(.�o?}�����S>��F�I���;~�5u���-���Fq�����@����L���Lw@<��l�p|�H��HJ���Z������Q\v����t|A&\�����t����k�
ZI�Ii��7\[����-�����eq1w
Z��S\���;}3,�6�eM����W�����Va������sIe�~� 
� �/�I��G���x�UK4@� ��N�%��1�oU��*^wY�����U�=�m���z��?S0u<��D�4?��kk�[�a�����,[:�u�o���CO>Pq;M������Q,ue��
��KG���W���X����,��`VC�0�;���#]� 
� i&x�@�\�"��7
�
��LOP};M�������EU��jkR� ����U��t�*�"I�$y{�@�x�i�!X����Z�}�����f�v�����U���f`��S��NsgZ�;�F�����*�o���A,���a~�i����-
�P
������������T����(030;����h����=��f`�����N%wKe�~oK]Y*�Y�Xc����_-�~ O��7C�Nt��J�����F�J���|�8�S�\�t��7
��`��z� 0������M����>��}������R(�R:���0�;-��X�E�������0�q?��j���r�(�B)��q'���0�g*���fW0�P��f:n���eIe����R(�R:���`��r�^��Q`�sy�A`�3��"w�M��J���;I��>�f���75
��`s� 0���;��{����rc�y�E|!�F�H���������u�=�7��(,�bY[��a9���O�v�YI��-�|�@
�@J�����\�o���QXv��z3z�f� ,�o?}��fgZ�W�oH�H����[�}fw:oq}B���+����	�r����>��f�;]�E
oH�H����[S�ki]6q��Ea����9��s��L�u�q���i��'x�@
�@J�����i��&���Fa���E<��x�����V[iUs�����/��F�H��~����������E�(,�bY���a�~�R���%D�F�H��~;|���_��{����eW,��d.�	�2�����_v��_��(�)��o��������7qY���+���w���L�u
����=�"�oH�H����[S��3�][!�Fa���z����;AX����8���=-���R R��������r��Z���KK�������e�������^��Y���_���;����]F�.g]�to����1F��� �������Mu.�y����7c�A5TC����U�S}���g7���
��z����s����/W���o4���V������Jl�j����t����������1F��� ���IvA��vU�'c�5P5S��@}�<�7c�5P���
�gk9�yh"�h�2���u���u�����qk��19��n^�u�-c��+���=��`T�R����q���
�������N�����Fc�6��A�~����l-g:6y2�Fc�)m�N�{����3�V�3%�Y�K��W6�WpW{:i��~����6��+c�A5TC5���P}��/�	�h���@=��`28���LW����� S��7��[g����?���Xo6�WpW{:i�.�����_��
������g�o��l��
��z����dp<[���	�7c�Li�v!��Vl��U��	v�H�O����)_j�i�#\�u\�������z��c"�l��6%�����c���W��(0�M=�ec�p�Ap������%�r��1���T���k��PM��l����vY��P-c��+���=��`'T_���[6�x��jq�w
Bu���A��cK�x���l���:��t��]P��R���[6�x���M��� T���t�������l���:��t���P�l�����1���Z���~>����?���@����t�������fc�h��Ah���I�juM���1���Z�����8@MvO������|K���1>�Z��N:���1Y�2Y�j����-������[..OqW���1>�Z��N:���[<����mSB��i��������v��v%���'cl�*���=��_H�)�*�-c�M	�z<��}D����~���,�=-*�o6���Vh�V{8���Po%�S��-c�M	�z����F�����5^5��1�f����o4��V`V{6�4���^j���Ec��)�V�4���(����G������n�x�1��B� ���I��:?W���]�l��6%��uF������n��K:����l����:��p��]@��8Y�fc��)�����*�@P��?}�m���_t����J���C��/z�l��6#��z���5F������,��
Y_eV�1��B� ���I������.�x�1���L�W�W(=[^�������K���p-����><\��Y\�W�I��\��H�R��hY��i������M�;}>�'km���4J��8��n���L��x����V������Nh�J����v����K�b�f;ug}�������j�&
���������,N�4NGq�)�!�>�tdm;�Ei�F�q�)(�}��r����@�Q`u
�zy�nqy=�z�u��j|���Nus���� �>@vdE�Yd�w�����3]������4N�t������m�~o�Q�����w����h����SX�����?B`�q������,0�f�<{+�Y=��O�u����[�nY��i���4�C8})k����=�,�������M���F��)��&�����8��[Wm�V�J��:�������eZ���1����\���7��8��Q�f�w��#]���Ei�F�q�)(��&����'�F��)��E&5���
������h�H'W�t�`����><V\��Y\�W�K��Z�����o�+��8��i�ek
�t���?�����}������l����!+��"���4`N�iUW��Y�v�����q:��4���q����k�g7Z�7k�������������)���fq����QO��8M�
��8�[���z�}����Yi�8�?�)������o�}:}�P_ �i��e��\��i-bwz��~������uY���{pz��V�C �,N;uZ|�z|a##N���(��������L�*�-{�}�������:���j:��s+��m���Ei�J�o[k��(M��x@�����u�#���^
Wq��;W�5V�N���[���^,p�|����>����eI��o��vz���
\�U�/J�����sW�o���S����5��a���~��;*�5�;��$�b*��|�j��B�����Y�v��z����Bq���~,��N�-m���EUTEU��\����3��:?��(�Ti�n�����(��jK�j��b�����h��$�b*��|�r��Z�;��x�q�b�S���n$�c4���i��"��T���,����k������9t���O�Hb����k��"��t���0 �+�O��x�U\����}��^l-�Z�7���*��GtrQ�i��>���rZ7��(��*��}����F���mn5�����CtrQ�i��>����Z��o���@�������o&�����.}v�j5;��jtR�J�?�h���������,��>�F3*���1Sg1�_Lz�������Z[������C�����4���{��n^F�+��v�"�m9>����U\��U5;���+�YR�~�(��(E��I��W:-���Ei�F�q�(���&)��0��k����n&�k7��WX�-��[�w�6�t�;�����Q&������u����(J�4JGQ���!�N����O�FQ�Qz�Y
�o��{�8$�$�v��z�|��wV{eu���z�NN�:5�����Q&��C����=A��4J�t���B�����k�Q������6��M�dq���Na�N�{��
�V�c$��XY��qk���E}�l�������&����b[�#�,N�4NGq�	�!�N�-�����h���qf)�����M���Q`�V���'��:A]�=M=[��
�o��j���Q&��B�|���k�(J�4JGQ���!�>�E[�V�(��(=����$�a�)NR�Q`�Vq��V`�
���l��h�H���f�!�)�YdEVd�2�0����N�d��]'T�(������D�0J��`Hj#��k�l)k?��U\�U��l��W����->�Q��Ti�W�'��a���~��Jvf��7�������������m�����,Nw��x����SAq������[�O�D�����l)�U\����]�Vv�7����>��D(� N�q��tW�U,�G��h��A��M'���JZo6?Mv;��w���Y\0~�9���������V3��)�Xt�$�")��s���;��e�#����(2w%����D�02�q����$�f���������Ub����]����\l��A-5����^q9|�i��>����.q��m�S������:���j�};P��%�c{�(����4�D�0J�~�}������@�2����J�����],������N�V/��(��4�o��R��J��}kUQUi�s5__�7��#&�$Bw*�z����Bq��{�e\w����k"'��7��18UQ�����{�M|+�F1�S����.n�c4������Zm��h����X���]d��e��Y��5��]��� tq�P�i��>��N��XY���e�UQ��;W���;s�+����{5Z�;����8F�|�}�}���������b���4_��_����!���Y��S��� ��A(��t�o��6���a�
��
�b)�w���-�����F��+����~0I����<Y�_�?�F�����Vq���)��)-��h�s�\���j4�(4C34wM���5��������fh���S�FE���jOP�h�����?�oM��t�j�{��a�&��}�`
�`����C�\�m�8��F����k�����3����&���9�5�Q�M��7`�(�v��z�{v��M��t�b�z�M9�L���S0���������.9��r�������!`>/;��
j����O@Ps�M�����oK��T;��	b)��_L]�j���$��F�L���9&s��y��h5�F����k�������tiOP����|
���l:7���r��iW�j�GOM�t�j�zy�lU��|�`
�`����c��[Y�E`5
���]����4��m�D�B34Cs�)�n�	�M��P�h���I�4�)����fO��G�}"�0����n�7
�`
�]��3�;7����H����h�~Ey���fz��Hm@6z;�m-�l��)��)=w���Ms��%�5
�]��#��9
��������uY���~�p
�pJ������|-���5
�]�,��z�9
�4���z���xV��)��)=w���{��iW�d����E��Bs���A�;�b�h�<kL�L��S�\_��v���7
�]�,.�������=W���������*v�����R(�RZ���rg����kK��$,w��6	�a9
�4�����6���c���F�J���;A���VlOZ��Q`�
f�����f:n���npx�|X�boJ�J��t\o��]\��F��+����O���L���|77�v���Q(�R(��N�q}a~�m�\~�����;�;v��L���\�5\���Fy�B)�B)
w�����������Q`�
f�����f:��Q�Ir�} ���v����5
�P
�t����s�l?���F��+���z
�������x���p�.�<��R(�R:��w+�b����Q`�
f����=a`��~��W��v���jJ�J��t\�g������v��z9�v��2
�sH}�5l�N��\���`p�*����?�t]�/K�xE���.�N�O/�@)���v���f�d���^��
��J�����}��T��(D�5���������K�����l��M��?������|����� �u�+i��>�#\�u\����ON��l�?�dc�7��j�����j����C{��1������A�	jp��|{��x*K��"S�*�u�~pI)������u���K�:��fc�p�Ap�������,~�j6�x�j��j�����Z���j4�hj���L��u�;O����1D��U���+��P\]��y�xUj6�WpW:��]P}�&�����oP
�P�t�8T_��e�5c�5Pu������n���:�fc�)u�~�y����3W��'�%���9�7c��+���?���.�>,/�.�O6�x�j��j�����9B<��Fc�6��A���tp<[�m�����l�!2����x�]g(����?+~�S�W���1B�\���NzpT�v���>��
������k��h��Fc�6��A���tp<[�)�]>;���"S�*~��K[�u ]�W�'�}7�oE�y��!�'c��+���?������>/;6q�j��oSR-�v��P�jz�|$�'��%�;c��+���?���N��/�W���1���Z{i��:������(��0���B+-x��u�����l��6%������8T����@~�<��fc�p�Ap�������E|�S�1���T����Bu���Q�^��u�E��!�
����O'=���n�����1���T�����8T����VC�u��������5c|@+�B�?�������l-�����1���Z[d��:�t��?�M7����zgc�h��Ah������m�>Yqgc��)�Vo�_��>�t�o�w���eJ���5c|@+�B�?�t�.�����:p�P�{� P������n�8�?�cVauV����v��j�xi�'c�M	�z��}D����~���,n.��O-���Z�uZ���w�f��5c�M	�����A�kk��\�o��6�m��Y�l����:��pv����:����[�1F��P���\jj:����6���$���1>�Z��N:pPov�sO4�X��i�B�li�ip���n��N����5c|@+�B�?�4�N����l��6%��eF+������n����/u����
����'����&+N&+��zS/2���(�t�o�w����������Z�uZ����u�����mJ��k�~����g���q3O��w�u��~���G��F�,�<@jY�uY����O������\�8��8���I��w:]V��5��(����SP�������Yh��V����Oh���q+��F�s�M=�f�d��R�������&
����v�+���4N�t����k�3i���(J�4J�3OAnU�����oZ;�U��~���Z{�u���|����z1F��r
Y�5��S��8��y��U�'��q��8��N�l��MT�(J�4J�3OAn8Q�OS�j��jg�=AX��	���!��j��o��� ��f\��I\�W������)����i���(N3�;���X��'�Q�Fi�g����ov����7���*N����Ak���[Y}OX?/[qL�l�������Y�uY�����t:�T�$=Q�Fi���4�C(��]��5��(����RP�M��[����Ni�Hz��
��Be�~"O��wC�V4���d�zp�6z���
dE�_���������FQ�S�/U���(M����D�DZ}1/�q\�6�U 3�:���n��p:��TY�8����l���0N�����u���,��l�wk�����
d�EV7i�=8�]�-�����N��I?���i�������Y�p���k���p��"+
�����������t�N�"Vw��8M���b]-%��y�6���v�Yi�8���^Oq!r��t�N�����0Nn���V#�uw��lY�55��U�W�*��Q������"���Y��Ti�S��(Fi�������5�v���f�<H�]M�������&���/[U��(Fwj�z�����q���~���*��u��f�S�U\����}}�^7[���7���*-��N?��i������b�3�;*�l�9���"�*�N�������^m��h�(�����B;�Q����(��)K��o���������t_��+�w���}�,Jw��z?���Dq��}��\w��7�s/E�����EUTEU����}g6����u����,Jw��z7���Dq���~�����XV+����m� �&���J�E����������t�J���`��i������m5q��FQUQ��;W�u6:Y��~�(�����D;7�Q�����������s
w��5vR�*��~Q���=l�mD5��}*}���FGi���������\�uo���.����J�E��������-��Q���h�&���&��g�����x���R�����6������:���fv�|�6z]N[�:C�fQ�Q:��?`�&��t�m/�Q��h�e~����/?O�D>Y`�V�������:n]u�H\������F/���i������o&����qI��4J�t���B����N��Q��h�e~���jD�����g}���%��
���
���+���U�St�b�xHV��*��j�)&~�0��,�P����h�}�0:-VV�V���=�������l��,�v	��a�+�NPW���Lv���5��A�G�����>��(�����o�Q�c(���J���%N��Q��h�e����jD����OV�dU��?����}�:nYu��b����oWqWcL)0�;��e��W��Q��h��a4��C�[N���h���Q�'�����g�����kX��U���	+��uU��<�F�
�[���l���S�����cJa�i�����*��o��4Z���bt�i���h�H��i��M|��F��������U\�U5��=(}��r�FQ�S���D� J�Q����#���s���m�1h������[I������
nO��rY����K�p���������
�8$k�������1{������&}���m�qf5��}*}�?�;��a����3Q�]i�������*�w�����f�����Q��Tiq��D�0Jn����V#�s��M�%n���F�*���~ L��T1;i�3�w[�����"t�Bk�D�0B�z��@�wK�"����V*��#�*�N�����^w��i�x%|�bt�F����\
�h�����g������(�	������z�����M��Z�����R�'��a���~�����������o����na,�R�.2��s����(2w%�z����?qd��~��;$�VqU�e�S1��;S�u���%k?�E�.�Vo:�E(���[�6�y�}3�w�mE{�m���PU�������5
5�����!tr�P�i�_ �;$��E�!�F�M�TL����{]��Nyn�F�K��[�Nn�#4���p���+��h������c�,���I��zS�*~������MB'7	������������M{=^��#`*��yg������-I�a�F�G���:$q�P�i��>��n�+�!���Qf0Si�3�^��l��,�&�F�K�������A=Y�]�=�������"��F}�g�`:>��Tv�v'�y�"�IQ�����5�?����O�Y����(4C34����6�vdu������<U/���J<��q���>�uI�_��]5�p
�]�0�;����^�F����k�����3[���Qh�fh>	A�m��<����E��4U�|O.w��i���[M]�Y���"bU�m�-��
����3�;�%��h��F!�!:�L�At�,'mq�B4DC� ��Vr�5��w�,�v��v���U\�����XVK�x��UP��	L�A��l7���Y�i��4��C �v[�6AR�
�=�����7�,���5��]����'���:~Yu=��\���5��c`���i/���x{�y����{�(4C34wM3S�C��VKbk�Qh�fh>A�m5 7K�v�'��]y�~v��?8<��v�T��<�F�
�[��/�����S0���
����������h�Qh��fm+���(4�s��#�>�F=�9wL�����y���*��w!�/�0l��	j��$Z<	�Bt����?���7�n�h?�eL���;E���9�%�����h��������L��S����C<��F�L���;E���Y���Qh���K�Bs���A�?<���1��`
�`J���������i3�5
�]�,.�������=W�������+��U��]���R(���;-wf��j���M�rW,k�FO���L���q����\����h��lv�TH������8����*5
�]���~q{����>�����l;o�2}�PJ������\��{}�(0w���=A`�3��p��p[V���7
�P
�t�	:��.�b�)n�x�����-<����������5y�&�<I^�*��t'j�����E���F�K���}.��	t�����|�}3���m�vDU�2y�PJ��7���0_��;k���Y����:�00�q��@\7<��Sy=�@J����z�|f�m[�sW0��\\�f��������D�Fy1��J����z]N���Q��h�2���|�M����m�qSS�2}�PJ�������XQW��Q`�	��^����'�t�o�{��&^ [�P
�PJ�������XZ��{G���Lq�I�u�)���d�k|Z��
����?����E����x������;6?�#\�u\������N�a�x}�'c�A5TC����U�S}%�v�	j4�hj���-',�������C�#�fc�)uU�����ptH�q�����m],����7c��+���?����^��][o���oP
�P�t�8T���%���Fc�6��A���tp<[���v��q�l�!2���u���:xt�U�q����9��,q�Y��!�
����O'=���]�����oP
�P�t�8T����m����
��z��0��g�������1D��U[���]g(���Kn�%�k�dc�p�Ap����������1�������q����D��1���������l]�b�:*k6��RW���'���:Cqu=���7��K�c��1F��� ���I���d��o���1�TC5T3<��i�����7c�5Pu������^��Y���dc�)u�H?�]��]q�p{�������_~+Ek��l���:��t�����:-v���|�1���T�����2������H�V7��h����:��p����z�tiG�}�1���T�Ow��PM��|�9��M��5c��+���?����N��Y��o6�x��j�s���������@���&^����!�
����O'=����C�fc���.���Bu���Q�^���S�=���!�
����O'=��KQo�y�1���T�+����jz�����@n��u���lg�h����:��lv���f���NV�l��6%���S��Gjp���n��k����Ik6���Vh�V8��}@�3c�M��:���i|���v���f��C=�fc�h��Ah�������zL��mSB�-,�@j:������8-j~�1��B� ���I��l�x7�'c�M	�zQ�6�@P����@\7Y������fc�h��Ah�������W��o6�h�j���\x�t���Uw��7�u����~�C�fc�h��Ah����<;��..����mJ��K�
������t�f�p�D�k6���Vh�V8��]@���<p��mSB�^hT��(�t�o�w���e��u�5c|@+�B�?�t����T7/�l��6%��eF���AM��|�Z�\��:��fc�h��Ah�������K=�fc��	���������=��[-n.�!�C���Z�uZ���w�!�y}�1���L���w����4������|����;-���"�B��F_�C 5���:���nv�'w���$�&�7��8��Q����i��������Q�Fi�g����/����E[���Bk����Y� �B�����M�f��m���e����"���4�.�>�����o�q��8��N_����5��(����SP����b���U�d��SZ�_c� �B������b9�w���6r$y��(�"����I�������o�Q��(��J_��;��$Fc4F�3GA�m4���=����NY��p?AX��	�����g�C����F_���T��������&�������Y��i���4��C8}v���Vk1�1�����������d[WQ����Nir���
�V����dk���Y^�!+�F�X`x�WK��ki��4N�t����k�U��I��h�g����j���h��>Yh�������Z������}7�oE��|���+�k��S����Y�uY����O��bEl��,Nw���)^?�q�����'����S�[Q�������B�Yd�w����9�K j�;uZ; �	�t�i��?��������������*�w���n���J�Y���iq���t�i�A7�k��N+<�F?}k���b\��U5��=(�-���t��t�N�"Vw��8M�
��b]�Z�7�[5\�U��l��Y��v���,Nw��������p���������V[\�U,�����f�]��v�puW�����N������Q�(����<A��4������b�3���o���R�U8�U��Y\�W���������(Fwj�z�����q���~��;*�6qD�l�7j�(�U\����_+�6q��fQ�S����w��(M��Z�]o���mk�����9�
UQuU����vat���7���*�^(�r�P���_ �{)��Su��6�)�g����������&���/;�x�a��t�J�7��LG���Ww��7�w��j�.���m���]������:���jv�~�V��RT��,Jw��z+���Dq���~= ]�R,�-����m�f��d����_��k��C���fQ�S��[�Vn%��4���p�MQ��C�5�HU}p�)��,���I�u7������K�Y��Ti�F�W��t��]��M��%k�m���TEU�/F������;Zk��TzSo#���(��t��?��w�]��j���j2�R�*��~Q�����]E���fQ�S����~p\�?W��N�t����[��!�Z�mY��o���Y�uY��������n�z�K��4N�t�'}���t��.m�Z��4J��8��6�v>W�kS��,�vJ�z!�Z��u�������]�6U��D?L}�0ud�EV7i�]8}X����,N�4NGq�)�!�N��U�	�(J�4J�3OAn�����6Q��Bk�������h��q+���:��K�~��\CVd�2��� N������5��8��Q�f
x��e��	S�(��(=�<����l���d��SZ���� �B�����I�`�Vs�TCTD�����Q|~�2V�@{�8��8�i�~�p:]��P�(��(=������^l��$�'���*nb���
�TV����a��N�F��"��B�YT�7�������+iF�Q��h��a4S�C�N[�����b4Fc�(�T�F��e����f��KX��_� ���PW5��`�����h4�p���}Z�6�d}xd\��I\�W���;������r���t�Jk�D�0J�~�`Hj#��_����j��F����R2T�EU3��=�[���]�������w��M���\�����8�	5��1���|������4��.�I\��F1�K���:� F�1��tq9-�?�m4>��d++�puW����v�����M>����t�N��O��t���AwR�l����I6����J������X-���5���*-.��(F���W�D��DZ�]�q���TL�T����|���S�5���-^O}��nj����������*N��(��*��|�j��F�v$m�E�bt�F����\�h������b�������*��*�w���j��zw?�}�5���*���Q:��t�o�w��fy��;��6���aL�T�/B�_��Nq'�E�.�V�
��*(�����?��������6�����0S'1�_LZ���9�k|k��Z�oh���8Bn����s����&�f�(B�2����J��W[��B���U|�F�K����~�:�i�����gb�����F�������@UTCU3���F���!���Q���h�������M���|���u�����R����4_��K��z����N�V���g(��4�o�w��j[�JS��*��*�w���j�3�+^L^�����z���Cq���~�����8�W��h���}����4_�������C��}�����C?X����=Y�]�������~c).��F��)�)����?��4��q~��w�Qh�fh���XIo��9�v]�tD�B34Cs�I�n��|K�T�(�v��zy�����0��[M]���j����f�N���y�t���;7���7
���]����4�li��5�(4C34����6�����E{C���iW����.���i���[N��r�l��%5�p
�]�0�;���x�b��38�s�83�;�i3���'����� ����t�de���}�x����t��S<�����X.�v
�S0���������8�[�����5�L�AsZ��� �$030���������/Z��d��+O�5'wO�t�j�{D�j�z-�S0��g
����U\�P�����5�L�AsZl]��m5
����� (���tO[w�����<�x�A<����T��	6�nh��6�u����(��)�v=o0����4����=A�BsW4��_?�p����c�{,������T�`
�`J�������/�& � ,w��v����(,�q���k�aE{�7	�P
�4�)�/�����q�(4wE����	Bs�i�A��^���Z���)��)=w���Js^m���ck�{�Y��	Bs���A7:\��x�B��)��)=w���Js^,��&j���Y\�}�`����������7�s�CYl?4jJ�Ji��F���|����y�����<AX��2
������![����7
�P
�4�	�����O=$�:�Y�;����00�q�}�
�����R(�R:��������5
�]���=A`�3�����pZ>���(�B)��q'���0_����Q`�
f�~���u��L���q��P�(�Z�7
�P
�t�	:�������=A�sW0����\���Ww��7�uc�iI�F�<�&���oG�B�����I���M�5
�]�^�sreO���_�G���e)k��F�>�R(��N�u}a�V��5
�]��^�sriO����>��V��X��Wd5���B*]w����Ix7��'	�]��^���#�����9���l���^�L@)��t'h��;�6+E\b�F��'��zuO���00�q��@\w�]���w�B)�B)w���{=e�+i?���	�6m�5����U/�����/`n%b74��CL�su��2��;�Zl[�����#�
�����g'�wv�7K��T���1�����wz����.�m����1�������A�i�p�*�����Z�O6� ��W�������P��[^]wo�M������#�
�����']��;V����1�����i�����]�x���1����������t��b��-Q�dc�)}U/�O.��k���[^]�����Kt�fc�x��ax���.��E\-\�1FX�5X3-<���*�#\�1�TC�0T�?`Z8��[�l]���5c�L�����	�+��Q^]O�|���
�l�1��:��x��{�z)�?L�~�1FX�5X3-<�9�uh��h���P=������v�f.��p��$S���=}����s�W��������~�l�1��:��x��{�z�l)�25c��5X�5��#a���-k5c�A5TCu�������V�~�1����j'-=AtE���j�=�F���4���i)�[�j6��Wx�W<{i��c��)�����qSb����:�t�?���l��fE���dc�x��ax���.���8�k6���km���HX����#z���C��o6��Wx�W<��]`����Zk6���km���HX����K^�a��N5��c^�u^���w��bY<����1�f��|w�#aM���>(�k�;c��+���?�t�N����xo6���kq���HX�����V�!\7c��Ul�5c��+���?��4�������O��oSR-�>s�:�������F��5���fc�p�ap������%u![��oSR��k��C@��W�M7��.��U�1F���0���I���l�z�e��oSR-�{��P�jz������8P_���#\�u\���w@u�S=C�fc��)�Vo.���(���o�w�����R���l���:��t�����8�%�5c�MI�z	����jz�������5�H\7_��m�8(k6�WpW:;��sS�m]�-5c�MI�z��H���=&]7_�`n��d5c��+���?��`�����<���mSB�^}tq�Q(�i��>����������5c��+���?������.�x�1F��P��]\z
jZ�������rY��fc�p�ap������)n����mF��z�Q���PT���}���e�gk6�WpW:��]P�qQD��oSR�^w�=�;:�z�����Qo(:6��;�h����H�b+�Na��������>���q05��H��q���tj��j�xnW��4N��H���6�~^v���,�v��z���m��p��:o>.�a�o����U5���:���j��{P��m�~�8��8�i&��p:���S[`\�8��8=�<��$�jE<����NqU����E��:���V���K����6z�v�/�.l��il��������|��Ej�F�8R3<��y���fLj�q�G�����
Nvd�n�O\;�U�U��+�NQ[}�<��s62������#S1uS�����!��S<�f���:��L!u>�����i����f)(�����������k�����]?8�\G�u���{�����^�\������l��.��w"u�/Y�;��H��q�f*x��l���X�Q��i�i���h*8�W���O\;�U��	�+��+�U��	6�P����_�mv�Tp���
[�5��8S��K}�u�S�5���J-~�w�IM��A���v-x/��_�6�a`�1�b+-��������f��S���'��������.�%W+[/�w���F3�>��a+�Nc�����.�>m��1^�H�������{p���V��w�f�uE��m�S(�C�Vl��V9i�]H}��VD��VD ��J]D)� R��t����l������Vl�VZ�|-�Y���E�~�H������w�I���[mzp�q�����Z��m$k�eM�������f'xn�/�Vq�k��t�Nk��� Nr������m����1Y��fXV}�����:���n��;pz�]��Z�8����e����#9M���wZ,�����l�����-ddE�id�w����zY>���5���:�-*�8�i������Xm]�w�l���C�[8�Y����MpN'[vu����N�V� ��1�8M�_V��e%�sw���/�9���J�����?X�l0J����MF���")��j+�\���u��lS���l#Y�Y����������#�fq�S��[�
�Er����t�_�+�I5�h~�����4`���o�e�T����N�Vo0*�`�i�����b�v����m4��)����4`��oN���;n��t�N��n/��4
�kS\�W��eun�f�-���N��!�<���I���������.����_��i�>����X.[�T5�����AVd���_7��Nu���t�N������+N������l��x��H���5�yy���[���_�N:��Rg+��5��H��q���tj�S�K�	�(N�4N�4[A	n4�'�Om��'���*^Bu�\�����B��8*k����o�x�������&��7[/u����i���(N3
<��)Y��15��(����SP�M���E;�����.iO �?��ZG�u���~������m� �>Dvl��il����������\�H��HGj�����:����I�Fi�i��
�jx���g��,�v���)>Ap�)J�����e{Q����c�l��)l��������X�7��H��q�f"x��]��vo�Q�G������V�x(D��k����� ���������L<�M�y�Y�*����>���������,N�4NGq�	�!��+�E�5��(����QP�Mo���=�'�]�*^�w�Z������}(�G������P�����eba�	����l��[�E�.�Wq�,�Fi�oW]������^�m�����*�N��������,g�'P�(���������Q���U��ur�R���7���������������
�E�.���/>A��4�7���r���;��h�/���Y�uY�������a�h��j��tzz�d�4
8����*r�3kGk��c\���pW�q�_M�oJ'�-�E�.���A��t���n)n�����Rl����m9>����U\��U5;i�S+�m���;5���*-�4uQ:����������r�����FMm������:���f�}��.��05��]�^+�r�|$�i��>��n��l_��y5�����4������I|�S�������'������~���(v+I[�[�m#��#�*�N����4_��:+^sQ�����B?�^�h�o���^q|�z�k}�h���<:�?�����o&����l���������?4Z��h��HFn��"�;��#q�?q���s�o����������:���jv�}�Vz�t���(Jw��z��=D����~=$=wP�b�.��F[�v�*��~Q�+�lW��(Jw��z��=D����~����]��Ao����<:�7U�Q��L�����R��
���.�Vo Z��(��4�o�w�b���O���r�����:���f�|{0z����7��}*���m�>Ii������b����}�h��gytpv������t_�WK����.�Vo�\n������� ^F�,h]v�vq�h�6� �OPuP�������s9m]�;��(8�38w����;���6��O�F���9�Te��_~�������"jW��Wkn.Wk"j���[P}o�\�8����(��)�v>{���8v�#�F����s������}���F���9�De���������P�x������?x<��q���8���>�'�p|��TAuT�����@�j��=A��4H�t����t�&�S� 
� =�����t�bI����E�.e���{����3V� �-��h���*��j�Y�~�@��,.�Q�i��4S�C ���k���(H�4H3AA�m��������OY��U;��	"+��PX}@�l��=�F(�L����������x7�gp��qf�w��n���5
�����!(��&x���U���YD�JT��{�����FA�@{��>�#i��_��C�	�(��)�v>{0����8�V��bj���Y����s���0&�!��/��|�p��)��)]w���{�y�����Qp�
g��{��g�����zN��m��F��"���}�:��h�y{@��#k?��.���=A���4�7�.�w�V�5�p��Va��������{�G�w��w:�����#)�S�j�Whp
���I��+�W������Qp�
gq!x�����L��{N�}����#�e)i��kL�Li��N�����6����h�~EYqq4�s���uSD�-"�oL�L��S�\_�W[�Ef5
�]�����sk{ ����>��F���X��5
�`
���)z�/�������BsW4k��� 4�������nz(v��5�p|��TA����/��%�L�7
�]����ssO ����>����m�id5�$��)}w����i����y�����@;�9p��Ve��F����+���C��(��)��s���;5��R�f�k���Y����6A3=�{L}oR;�(�K�7�K2PU��T}�����C<��Bt�D��\�hz���{a�jI�Q�L"�)��w����4�'m~�F��+���~~p�4�s���u�U��	5����*}w����O����o�{$�P/�9��'���o�w3�a�"n�x�L"�)��w����4'[Vq=����I��������[\��������&���vjq��i����w���m�,eq�Z��#�
�����g'�wv�W��63���q`
�`�;=i��X�_^[�3u>�#��z������7����%���1D��U�!�p�!]��u����ox[[W� �O6��Wx�W<i�=`��Q���1�����i���.���c�Fc�7���a����p<]�}���x�dc�)}U/�?\.���~}�����L������#�
�����']��;O����1�����i���~V�����l��`=�������ov�[�>��dJ_�x����s�WmL4���2�'P�1F���0���I���k��k6��k�k��G��d����5c�A5TCu�&���������l�A2�����;���:Gyu=��9��Z���l�1��:��x����z�mW��1FX�5X3-<����i��j4�x�j����L���=b�n��dc�)}�V�<A|����j�=�F��{��/O�]��6�fc�x��ax����.<9��]��5c�M���;��HX����C�>�v�f���c^�u^���w�����kj6���km]��HX����#��i��\���1F�^���O�pXg����l�7%��R�'��������{P���c^�u^���w�u�e����#nF�E+� XG��.t;�z=���o��1B�\����N�pT�b���~�1F��X��c���o��	+����[����\mY�����1B�\�����N���T�6����1���Z[t��:����?�m�~)�'c��+���?����.v�[j6�x��j�V��[�CQM��|�`�`E<;�fc�p�ap����O��XJb���mJ���rq[ T����������������
����'-����K}gc��)�V�-:��(���o�w���������#\�u\���wAu)�F���1���Z����PT�����;������b?,]�#�l���:��tv������O����mJ����N.?
E5=����u���l������1B�\����NzpTg����mJ����N.>
E5=����zq�Yv�;c��+���?���.��}�1���L��\x�i:�����b��X�5�5c��+���?�t��>m�j~�1���T'����uG�����/�n���*�l���:��t��������ko6�x��j���������O�J�/��e���.K�x�B�6�k9�:Dj[�u
[�����.u�����,R#5R���t����.�]�1�5��8��#�VP����W��Z4>Yp�W����q�'����������U��~m�����,�EVd�BV7��=8}���7�=Q��i���4��C8���C�	�(N�4N�4SAnD�r�vj����k�����'�����_\����g��-�z'��me�<Dj[�u
[���� ��X��1^�H��HGj��������	j�q�G���7�
^�m��-���Nq��	�+�NQ[�!�jH��-�8*k��O�$��7���:���r����z�K\��Fq�q:��L�t.�����5��8��#�UP�������"�����k�����Ap�)j����fWR���F�&�%[�%�Nc�����.�>���)�o����H�T�R���U;��Fq�qz��
Jp���s�$����k������Ap���j�=�F��{��/�N[��fo���/���EVd�BV7{���;����UJ5���J�-|�HHj:�|(�G������8�h�����A���Y����M:pNov-��-5���J-^xr�:��t��������=�K�k�������b��Nc�����.��mW���,Rw*���m�����G�����-���S��~
l:�Vl�#��z+�o�5��}J}�\�A�$5-8�~��KuT�6�)l�u�5���:���r��{�z_m)�o�5���J-./��HH��-X�t�j���~���y�3�5�H�����Y�����N:��No����U�Y���i������8M�_V��k�e���l#Yy�E�"+�N!���4��N��Mqo�;UZ����2�HJ��}�}w�-2�o���5y��(�"����I����b���fq�S��x�8�i��������P�S�6�)dy��(�"����I����������N�V/��|(��4�o�y'�aiSW��Y��!+���gk��N��w��7���:��ctq�Q$�7`my��k������z�m4��Y�����_��k�7[�k<k�;uZ�����HN���H|wW��$��y�6��s&�Yi�8��p>�^�8�����E�Er���8��8m_��eo����M���n
Y����MpN�7��,Nw��zw���E������+-�9��j�<���+�uY���w���nW~�(���Y��(skQ$���_����b)vdq��f��M���"+�N!����_�������o�;uZ��({�Y���|
xQ7/C�3��VNq�E��B^^_V����S��/g'xv�O�v��,R#5R���t���������KP�8��8=�l%��_^r2q��Bk���Wwf��;��_Z�-����i����m��I��������&���w+���Ei�F�(J3<���f���j�Q������6�����R�,�vI�zs��rC�H��[Y]O�)���v�@��!�`� +�N!���4�>���fL��4N�t����k�M{�7��(��#�SP�[MVN�G���k��j��\�(���h4$�be�����F/��s�3����4���I�D�M��FQ�Q:��L����qj��(J�4J�3GA�mT�bI��oX��U�gz��
�SV��0;�z��m� �~����:�:���r�������R�l�H��HGj&�������u��4N��H���F��w�������Nq��x��
��Jm�l{���������m�x�m�3�����Y�����^:��Ng��J5���:�-|�8�i�|(�G�H���*.2{��F�~������:���n���p��h�Q���iq����@N����svL>h�Z�^dV���4���I�����.u����t�Jk�Z� JR��u��j�!�*��6��u|�I\��)\�W������I<���t�J��;������F�Y�������h����M\�U�/J�7������Q��Riq�D�@Jn����V�����X-����m�*�K�*��}1�/p�lO�����.�����h�����N���"���QTEUT����|]������N�(Fwi�z��>@�������.���[�k�Q���,�"�����o&����b��#�F1�K��x��h������-���7�h|��	�+���4���I��A�#iOP�(����%B�K�")M���|wPvj����F��9���4_���Y�����Q���h��L0a4�WPU[�u�}7\��%[Nq���6��Ud���4_�������s�o��4Z�}�p�P$�i�_$�{�V��xb�m��|bd��HT�FU3i��Fo����=I��Rh�����C����~���%;N�7�h|l�tB��*�N����4_�K����FQ�S��{�~p�J�~�w�w��a�x�C���`x�*�N�����_�K�����t�J��W~�J�~�w�w�[�M������.�+��U\��U5i��J�VQ�7���*-.��J��t�W�F>�F�����m��
k��c���@T'A���N���D����}�(DC4D� �f����N��]{��h���A�&(���o�lQW��,�v��x���U\����� �.;���5��c�>8��8��������M{�gp��qf�w��i9k��F���9�De��w��l��+j>aL��T�V�;���:CE��~1��7�h|��\����O&���/[�	j�A�� �T�H�dg���Q�i�fz����//����s�jY��U{�'���:Ca�FD��(����UX�� �
L����f%����(H�4HA���!�N��5��9�Q�i�f����h��J���Af5��]�*.�.?�:���:nau==v&qA�m5�)9�
����O�����N���h���D3�;��\�x�z�B4DC� S�V�����]{���0�v(���AdE���j�=�F��{�lF!���jTATC�(�3�;;���
�F!�C����O��M���E�Hi�,'�6�(��*��{����D��b�}��!�Y%:Ct����?��������m4B�;��J����t��N �'��O�� M�
�s8'+������*��*�w�����a�)�m�FA�G�W��wA:�t��(�jyY}���kQ����:	��d�|{ ������Q���hq����8D�����V�
��W�s�Zo��z��2H�TZ/@�/�/KE[�P��!��2�'�q���~���n�X,����;�h|$yt$P�IP�'���O�a%��l�Q���h�V��[�M���|7M���7��3H�TZ�D���5��9�Q��h�� @����5'�;��S>�����R!��;Q��z�\���F�C����Vn
4�������b�}�L#@*��y'���@o�vq���V�����M��{R�[w��w�s�DY�/�Q� R���f����[��<���V�Z�N(�t��?�M���=A�2���J�����][��;m�t�@�	�`�8@�y�'�u�D�l;5j�iH�T:�D��w�ei�Q��!��B?8��������M<�F!R!��;Q��:����5
����Wm\h:����E�*�����m�!h�����SI�u��lv\�o�5
�����f3����6���Z��
����?}��MW���};<�Fl�b��B��#�
�����g'�wr��������O6��k����I���z]���2�l��`=��ZN\P�[}7�b�x���1�����7jn.7j"l���[`]wo�j��m!�dc�x��ax���6�	���fc�8�k�fjx$��%�������1�������S��|-�nG����1H��U�t~s�t_��u���z��3 ����#\�u\���	wA�f[����1�����i���^����N��#��z��0-���;���O6� ��W���'���:G}u=�r[�Nuw���1F�^���O�pX����25c��5X�5�#a���W�m@
��@]��I�x��T���)�l�A2���qK����C�:nuu=�}[O��:)�fc�x��ax���.����U�~�1FX�5X3)<��
�C=^���oP
��P]��i�����Nq��?��dJa�cL� �"��S_�A�};��E���Z�H�#�l�1��:��x������l����l�7%��/5w�#aM��Q�
�v�^�����1F�^���O�p'X�$.���#nJ���� XG��.��#���vK���j6��Wx�W<��=`}������fc��)�'�~1��t������;����l�1��:��x����z�R�e5c����.��� XG��.uC�M�y�w��l�1��:��x�������'.�l�7%���4����5]X�U����Wl��G�����#\�u\����	�N�q�T���mJ����;����=&]7b���*�5c��+���?����NV���?��mJ����wn�E5=�����qd[����l���:��t��;������r5c�MI�x���HT���}�
O�M���7c��+���?����.V.q}p��oSR��^�s{Q(���_$��/�n{���5c��+���?����.�����5c�MI�z	��%H�����U_w��w�u��~�����fc�p�ap����<;��{p��oSR�^��sR(���_�I���a�)�#�l���:��t��;�������l��6%���G;������8o�X�n+�s�l���:��t�����������l��6%���G;������n��O;��k6�WpW:���T��U����1���T��G������G���b�v�)��7c��+���?����.��x�O��oSR�^y���o�9�'R�W�u�2�-E�n�z�l�6�k�Y"5���:���rv��g���-�W��,R#5R���t����.�q#G��4N��H���F�q>'@��"�,�v��z���q�'������������M�[U��l-�l��Vl��V9i�]H}�����Y�Fj��#5S�CH]���tE��4N��H���V%8e[�\���)��
�;��:Eq�=��,�*�o��WS"'{.�u[���w!u�u���Y�Fj��#5��cH�,vd����Ej�F���+������V�TW�Yp�W���;��:Eq�=����H��5����t[�b�,���I�D�}���Y�Fj��#5��CH]v����i����f+(��J�n���V���)��/3��C��H��[\}�cO�?����l�����h�x������/'=�����.��V�H��HGj&����\vq2���4N��H���F���9���}���)�I�5�+��+�UO���C�^4���d�7�l���P�H�b+�Na���������7K�z���E�N��)?�E�i�l�=�r+�-b��V3�A�5���:���r��;�:�?�O�;�Z<����dR��G����}���s�5�j�A�tQ����S��/'-���l9��Vk�;�Z���"u �i�Q7�^�j��V��7�n���il��������:�k�����*?@�i�lu�uqZY�Mo��Oa��Y�a��il�������*�K��f��S����w�I��[�[mzp�q�f;��*�f[�/��F�Y�����N:��No��j~�(������w�)M��zH���X/��~��m%+���Y��8���w�#�{��,Nw��z-����������,�l��{�j���Y?s's��N#���4�.�>/q%p��t�N��� Nr���)������ko��S��F�Y����M���k�k��Y���i�
��+�"9M��#q�_qk�����l�/&W�#+���q��5��;}gq�S����Nn2��t��-��s�����6�.�j��dEV�����v����� �,Nw��z���-F���=$}w�m�qU�6z��cdEV0N��������,Nw��z���
F�����;,�����l�/�&�EVd�BV7i�8}Z^U��,Nw��z{���E�����;,6+I=��6�)��Y������j�&�u�Y������\���(��4��?����z��m�SX��e+�"�4���I���K��x�8�����E������k����x���rY9�
\��F���
Y�uY����O�t�M<��Fq�q:��?��>���u��i��k�q�G���7�����|��]?ax��W���q�'�������u�q���B�h�������M\��U5��=(]L��Z�(��(Ei&��P�:���15��(����QP[M��e��������'�{�A�_T�-��'��l����l������
T�FU3��=�����Ei�F�(J3�;��W�S���FQ�Qz�
�o�1y$��uL
�k�����Ap�)J�����|A�6��h�����Yg���M:pN;wy���i���8N3	<���ay���Fq�qz��
*p��f��Y<����Ny��	�+�NQ\]OY/��S�	�(�"+���``"x��M[�Q�8��8�i&��p���}uo�Q�G���7�n^W�M>����S^���;�����VmL,�J���h4�����i�7��1�k�U\��U5{��s+}��V\
�FQ�K���L� JR����R��\=�:��v�h��c����C&quW�������e���'��]����1:��t��������]����������\��U5��(�.��,�~[�8����o0O�9M�
���\�!�W�C��M ���dE�Qd�w�������q���j����zw�IM���.���M.�h����*VV�!�4���I�����k�%�PX�H����B�;����������v�z�����Sk�5�����TE�iT�7���;��O��Y|��E�.�V����|%9J�}�w�{_��iOP��)�*��~gk��J�(�����5���G2����x�~+����j��	����^��[��:5��]-�Z)?X������U�����M��V�	�q:5���:���j�}���/>.-�=F�?UZ�N��:�HJ�~��H\���v^�Q�o�����Wqu
W��������,�Ibt�F�w������
�j���F�
�]���v�7�jNA>H�FqW�p�_�N���J����k�;UZ����N�HJ�~����(v��������]^M���:���f�}��>�<�Uo��4Z����F�HF�|�}�}����a�6z�Q�*��|1�/�.���7��]��D����M��_U�]�%�r�'��!��QXE�����/&��_���"�F1�G��z�P���HF�{��H\�O$��6]X������l�M�N����t_w��e������,Nw��z��.&��i����.���e��r�E�V��6�!���9�:��dv�}''z�C{��&�:�?�����"�+V�
�=�����_����C���F�eU���.Wn"k���[W]����E{�V��F��*�7i�:��h�~{@z�,��X� 
� i&~�@���:5bj�A���������=w�E�����.mU���.��ck���[Y]�����������j�Y�~� :/�q���h���D3�;���x���h��A&&���&}���?ad�PV���;���:CYu=9���q��m�3P���@uT����v@t9lS�_�
��h�|� :�����7
�
��LMP|[
�����6�0�v(��X�"+��PW]�L��(�h�������*�N��?�t�������5
�
�!�f�w��]�Wm+t�B4DC� S�VCr?m?�
�	#k���a���Y��]]���l�����f;�6&k���@Vu�UX��U4{i�s#}Y�E��(Hw���Q��t���0&�!����V���7��1���AT'A��L�oD��+�k�;$Z]���|�6D�{��{Z�e�h��l����TH���_����/��(DwH�6��!:�������f�������#D�
�3���&���w;/q����.���t���A�O���&�F���FUP��N�|}��l?�Y�������A��Ct����n��p�6��%��$��4��J�����8��|k��$Z["�!:����G�������hOP��^#�xR!�����l��~5
���_�>�t�o�u��s	�8�[����l�
��:��d�z������k��$Z[E�!:���o�u��-e���(S	�
����Z�7�e���F�C����
�����G��Q�v�,���m�l��TH���_MJ�����jq1@�	���B?8|���O��f��5�n8o��Dj�iH�T:���y�z�}���Q��h�*��UB����~="=�I�l�x�W�6lUP��B��%�����o��$Z�L��R!���=��%�j�������B*�w���
���w95
��^#T�F(�t�o�u������^�m#��#A*�NB�?�t^w��eW��w�to@���;G�]M���#��&���S$��B*�B*�w����i������+�6O�5�#�����?A�[��
��?y�iz���|��>�����y�+�j6�(X�X>;i��s�f;w����1������~����^�lG����c��z ��������{��m��c�)��Y~pU*�f��%�ug���&�P�1����@���I��u���Z�l�1�p
�L����\�?L�j�fc�9�������q@a��X���1���j�W>A���YJ��!<���)�����%���'���w;�8�k6���k��k�������=����p
��M_P�[
��X���'c�Li��A.�!b�`��[b]��|�}����%���'����������sp
�p��X\?���!.q��c��z ��0EP��v;��7�	�(S+�U1cg)��G�ok��T���l�Q�;��|����z�U=�fc�9��k�f�x,��i�}���dc�9�������q@asZ,'�-�'c�Li�z�����������j�=�F���4���i����dc���������F<7��jIt���1���Z����p�k��mT���I\��Fc�x���x���6����],�5c�M���7��u,�i��?��Ai7���#�dc��������>�	����5c�M�uQ�.p�k�p�=����L��,�������\_�M��Z���s3r�j?�'����G������>|gc��������>���&�O6����km����X\���~�V�\7g�.q���1B�\����N���T�w�������1���Z\�~�:�4������������5c��+���?�ta�����m��dc��)�O���`k�����)��,���5c��+���?�ta��b{�6|�1F��XkPO�caM��|7d���������#�
����']��+E;4���1���Z��h�~�`X���}��w�'�d����:��p��;�:���Y�1F��X�W$���V��k�j��\��u+�~���i���1F�^���N���X��qcG��qSb�^��rAR0���_��O���~�1����@���I���X��{��h��6%���H+W#��&��8o��m�T]�l�1��:��x�����O���o4�x��j�R��K��QM��|�a���$���c^�u ^���	w��s��^��l�7#��z!���H�����[1���S���fc�x���x���.��w���s�k6���k�B���B��g����-y����C=X��6�q)I�z�(���$����I��#�wl�Y��j��d���W�[]���+�Ej�F���,(��F��+I���N�Uo�<n�����:_�yY��a�o����%�_�EWt�DW;���X����j���#Y���V�l{�6Y�(R#5R�5gAn5)|����s�?a��X��'�;Iy�=���l9�k��l�Wn���F�]'���N�pV�v����o���#Y���V�l�%O
�Q�Fj�k��"�jR�Z,�+�k`;V��_~p�?�����������|�
o��+���_��QtE�It���.���eSwu�Y��j��d5��CX]��E{�Ej�F���,(��&�7��u�5�����:��^'���'�����#�d��]����+������4�.�>��_��Y��j��d5S�cX���b��Y��j�k��*�hT�#����5��N�`��#�X�����oO����}&���}���^�}��^���K�]�u]����On��SC\)\�X����/4w�CYM��q�
�V�.���T~�m��� �QtE�It���&���[�#�M�f��S���w�CYM��|���7��>'|g�Z�y�EWt�DW;i�]X�X>�{�j�;�Z[���:��4��[���h��6�7������q;�]����N�pV����?Y����]��=A�e5M8�>�g�@�^��	�� 9�~��+������4�.����{:�f{:��O���?A�eu�&�nHn�
�u�v�q���j����F�Vl�#��:����5���J-.6��HJjZ����w��ee�vY}���8����Vl��V9i�H�Z�4d>Y��Tj�"����cIM��|�^l�-�:�f[�0}��`+��c����������k5���J���?A�%5-��p�#�����f�}5���:���r����������(Nw��zQ��EE��������
�D�Y��<_vvz�"��O�;�Z��h���XR���Z�;��Cq�o������h+Y�7l5���:���nv��gw:m�#�,Rw*�z��&����V��k�E���6�cY��9�bG��d�w������E;��E�N�Vo8����HM�`��^������^��l��l�VZ0R������E]�f��S�����?��e�����A@i��G5ht��i
5)�T2I&@2��K����J�<�gy��g� �Dr�n�{��X����������`p��&Pn=���U��*�[�[�����U�L�X\���s2�A7�l�S�
��*C�-�'������[��;�k��[�����U�L��$sb��'ejt����H2S?Q�U��YM����)F��� ������v������s�!��
.X�j�j������d]=���"p��d�f�f����`��������t��	vR�E�|�K>�`g&�}����	\�q [����r(3+3�C�u<o������X\�������+q5��[p��F��L�L�L��_�B��!�EN�8�L��,������	v/��W�������@��xp~������p�x�d%<S��!�N.X�j�j�����-�-�:�c�e�f�f����`!����"H�o`&�I	����L�L��CGS�@&b������+��Y���y�u�<��J�e
�y�y�yz�f3x���v��,�,�,��O�����$i�a'��uJz��g2�����!�u�l���(�J�@[�&L��w�3�>�[�3'��	�:�H���������+q5��[p��,�
(35353�^��F����X�f���`�$ ,l'���[6�Y����[.�
|@�y.;3���u<o�������a��<=%Oc��d�^��Y"/��hDi�h�h��Txp*�V���p�x�d
<S'aka�"A��'ej�1���bjV��x����j����U�m%�a�1�2�>�Y��&k�x����
/P��)y�����K�4+�E[��#�Af=��B��O2�2��f�~��z��zN��������K15��E�-�5C� ��� O����� v������jE��yb��'�j�:����Kq��Jm<nu��oa$��*,������3�>�W���$��,�H��,3�YzR����
�,���~W�������ah#z��#~��������x�d�;��� ��b�e�������������~�`l��&iA^=����
�]����x�d�;��C$�(���,���g ��R,�����vWKt
��1�A#�@2�2�>�W��&���,)�,}@���dit����Ck�4����"YP��h����(3+3�C�u<o����������<=)O���,o.Z��V�XYW�5�Q��T(�`OEF6�$�����W���Ys��h���g����<=)O�[�nXg�<��wf�U��(�>���+������������w<KK
���YzJ�F��`41K��]�W��R�l���W�wlJ����� f������O���;�����4����6s�iV�+0�����H��h���Wl(3+3�C�u<o����B��P��9y��{��)Z��Y_=��8OU7����\fUfUV������ .�)Pf�)Y�O���'b�~��h_��q�P�d"�~��1`S�-�����x��D�>��
��e�f�f�^��o`MV��I�+��h
�I�I�Iz���o��NY2
�nz3�N���zN7d='s�����d����s��p@�&�'+�6�:�6Y�@��<8�7#�������!i�� i�I�4�4�4��I����,#�}o`f�)��Q�f���u_�:v�� 	����6��pW�*��cHu<e�����%Ep�Y�2I3I3I/B�l�nA�>�G����I�I�Iz{��o��RS��v�70s�������@�V��gH��%�'��>����X�X�q�����C�^L�(�4�4��24��4�5���H&i&i&��L
�����%9�F�
��:%�bfS2�2�>C��4i��F��o����L��!����x�����@��������i����i�I�FE�2M3M3ModS�nd���b�
��:%��H@fWf�>�#�l��`�I3w!p�m�#x�0�2�>�X���,��4mI���2MOI��O��L�+�4��O�����	p��ms^.�������I�������$=!IcO�d�^��Y�~���R���GAAc�@��:��aZeZe��$���
d���e�����7��$�I��]��88��{8��B����X�XY�2M��iA�{@��g�i�}y�4�M��]��"��`W�mt44�*��chu<i�������5�(���$��d ��J$���E��[E��V
���P�l)0�2�����}M��d��.
�IzJ����Iz%�f�{9)�6RHr�+�ms.&cReRe���E+����=!E���=��_��Y�^=��&
Im�8�
3��U��g��x�d�;��5��	�=%Ac���A���z�������	
�i�i�i�u��t�X��46��@���$its���AK�4k���<!I)pd�m�\B�������I��j��P&�)I]?�y��R$����^�'\�ed�D^�&A�{@}�L�����ZO��h��������e������C��-E��}/'��
aK!A��(�T�TY�2E��j�)�#A��'�ht����CKQ4����>���dBeBeBe��(�;��
	t��e�����uC��
-E��y�`h�D6pH��Q`*82�*��chu<i��O������$=#It�P�eCK�4k���:a���a�2�2�2���}��K��\+(��{,	V�5jcI��.
C�
�d�ih���$;�_����F?��
�
�5/�5��)�)v+�O��h�����<��F�1a3a3a���������"8�����uL�L�[v�-M��><(!��s���H�G�,�����yv;��W��9VZ���
v�<a�e���b�(��;���,�l3a3a�G�loA��67�v��c�f�����?`�xA���yR����k$�#y]i���g���g���C��d?!��p@��&X&��v<}�*���=I.�,�5��	�	�	����[JO$��F�1a3aoE���U� ��h=�K��^#Y��`-L2�2�>G��n����A	T�k�	S,S�V;�@YOA��,�
{@��9�k�k�k��w�k)	����F�1a3aoE���U� �!"9��z�dy$���0	�<�<�);t|.����F�0�2�nE��	�u�$�-�!�`��:&l&l&l6�w#�<V��C�N�Y�����a��f����$XW��^#Y���'�g�g�IY��2����(�>��l�+�5��)�)v+�O������O:,�(�5����
@�0��	�5�2;v�e$MD��2v�<a�e���b�(k�;�\p"�]#�I����t�]�"�~��C~�:�@��&X&��v<}����=Y����]#�I����L��6+�U{���U�X�&f�e�eM�`M<��=\��i�:b__2]�F���Wm���o����F�0�2�nE��	��$��*[1aOL���'q��'&l��(��=����C[5�"/�:��]#K�`�`�"���9�"~6]k���S�k��#�,HO@������������$y���]#K�`�`�"����zx�V+������t
��M@���������m�H��4��\�kd	,�V;�>YO@���-��]#�I�X1z2]�F����`l����<X�V�kd	,�V;�>YO@��bG��9�H�F� ��#2]�^�`�6hdm�(�5��	�	v+�O���'�kM���9
v��{$]���"�RZ����XEX�5�Y�6hG��>�kd	,�V;�>'��O�k�n�+�5r��t��Q��Fi9�f=|91�6h�@.�{`��&X&��v<}�O�V�G��`���G�5�F��w�L���!��
����?��kd	,�V;�>YOA���t��k��#�]�y��rt�z���6hd��3��5��	�	v+�O�����k��yk�F�=��%���;������";�A���?\�kd	,�V;�>Y��k+�	L���5r��t
�OJ�tm���)�����F?��
I(t���m�����Y����������9�~:[[�`�������b�����l-���l�l�l��w�r�Q^z�)�N4���$�f& �,��cD�����EG��V����,P�W�����x�dE<[[r�
������z-�f�x������
������z7���p�w>h�l�}C3�NJ����d�e�}��;��Y��yt`��+�V������������x��F��e�f�f�^��� ����#����\�\�\��s�b�Q^z5���|C3�NJ����d�e�}��;��Er����]���3,�+����u<w����=Y�
�l�l�l�[�9�[KC����`������ws.X�����d���
�$;)���c2�2�>F�����YZ��I-���@�_�_������l��H��,�5�5��Zl��l�$�N�/Xfkfkf������bY����d'%Y�\��U�Iv7�m'b1���F?��4�pc)�a�`[���|w����_�����sE�p�vd"����zR� o�:g�f5�
���li	n�(�Vn���������������jx
��$��Q�����u@�:0[/�����a�x4c���S'����p�A1�2�>�_��'��)�ZQ��n��e������*��]*����W���� g
�m8������j�_�_�������lm(��{
��zN����������5��E{3�U�
���`b����e~e~}��gOV�S��&���/Xf�I�+@�@f���za5�6*�j����yp�m�4��w(3�>�[�3�$J��LmH��E�2WO��`�y2W/����/���~�Ix�]l3����]�]�������0s5s�m\
��K@�������������j	�V>�s���������a����Jx�6x�\�2WO��X�K2W/�����`l�V������ghF�2�2�>�]�s'+�	�Z������e������F70e�f%�����H����#
��:�O������b����Jx���%6%��2WO���V$�[�V����0��)��,C�0�#�val+v�ht(�+��c�u<wN������D=��e������H�7"�����/���.��8�]l+v5��`�g`v}���NV�spu�x�2WO���6$���V�jV�W0��`L,/Ol���BZ�#C�]�]�������\mH	T	X��I���tC�s5+�5�ulFR�h���m��<�������0s�?�d��.X��9�Z�[�oAZ��Y	_���]I�j�\3���6��,������I�:�9YO���/W����r5�I���t�
�s��:|����~��]R�{�Nl��D'�H�f���,~���h����"��7,�5�5��Zl}}������"k@m]��������y,��|��1&���C�
�$;)���A����L�s���"vls��d����ms��������1�:�;Y���lu.X�j�j�������-�::k�.Pfjfjf��<���a�E�h@c�
�;%�bO�d�e�}�|;�GJB���V�)����'�+��N�:�;Y
O��y�'J�������z-�fcx���XoQ���������,�[Y�BX�=�f���b��d2�2�>F��])=I�Q��m�����e~e~}��gO��S��%��a.Xfkfkf������-�:2�>��������9,���VE����IvR���5�$�$�	;v����h�*<���3C�_�_������lm���:C��������j6����h�XLX(s5s5s�n�K�F���2�S�~C3�NJ�����L�L��$,�q����~�VvC����l��tx|:�W����x��E?��%�Ul�Xf�I�<�������W�������0�&l3�/E�\����$~�����`kER�j��2[O�����d�^��Y
�����7
Z�v`�$��t�HfWf����x�d-<WKR�*q`����j�}�@��������m��=#���]�E�5#�]�]�������\mHH����2W�����2��z1�f%�hO����D+$l��x|�W����x�d-<	[;��8�����5Vx��������jmPn�1�C(r��(�V�����������_F�!
��9����r5Xt�����q5+��y1�Ch�>�`�s�p����3��'q�x�d<S����<=%O���5��_��Y_=����k�G���[Q��@2�2�>�[�3'k�)�:�D����i��\�1��y�5��:��"�������N!'�������b����*x��+��wv��zR�F�i^_�W��z�����1`�[�6r����h�V��q�x�d<S[R�Q���S25��H�����za�1K�5�YFv]Dh/�F��hhHfVf��0�x��D?��=p�}F2KO����#��Vci����bd�E����j;W�1�2���e~~%9�����<=%O���4�7Z��Y�^=���h�i���@��&:��@�[�[�����5�x��$l�&(3��L��6��a�L�*x
n�c	���*4>sR�����$n�����3����',s��\m��F��������`h�E4��h+Sm4>������a����*x<O{2{�S���S�4������<�4�H,�.c��"IZ�C���L�L���x��D?���_,�K2����T�T�T}'w��O�Q��`��?�����7"��Z,��|�vJ{�3������G2,���Y��;3��+^�v2|�v@[gE�S+S�s�u<q����#)���@������"j��� ��)��]�2Q3Q3QoeV�nd�Y���743�����7Cv�3���������x�������F������ bO���g��@�$�4�4��2$�F�$E�i�e�f�f����`�����R�T�����uB~�~�d~e~}�p:�2���M����B"~���� bO���g��H����i�i�iz�fx�V�:�n�4�4�4��1��A8��>����+6�4�_�_�"\�b?VI�`8��b��4L�L��!����Jx���(�������z!�f3x�$����2Q3Q3QoeW�nd;�5	07���a�dX�2�c�e��%^1n��F?����
MZc�pb�h��"r�S���u<q���N�.
>�L�5x�d�^��Y"1��h�*6(�[���L�L�������x
�V�V�|b����j���L�kQ5��O����r5t�e�,[9�ht�������u#jO���� jI�c/|N,��D���g �ZD�x����[���|`Q+�/D��ZD����5�D��J� �`���#j�}w�D�Q�^��"�8�SP'��A�C��;�V���P�x�d
<QK

��O,��D��b2��z-�^X�]���cd�EL�]�y@C���dZeZ}��'�I���IZ�� IP&�	I,'O@&��H������b
Y���+�V���v�X�XY�2M��i��@����it��M���4����\%yp�V�6:�J-�+5����x�d�;��y���@��'$i����L�k�4k���T
�.�8�l*0�2���}��K������e�����}C��
-F���`�"7C"���e[�i�i������h��T��IzB�F�Y^W�I/�}1VI�F?��v�`����@��BiU1�2�>�V���$���$H��$=!I�k�,�)Z��Y�^N����+���mD�hd�8u��'��x�d�;��E��o�IzB�F�����I����:��"X�`�h��::B���3�>�V��&k��$�I��9�IzB�F�Y^J�I���z���I���h���e�L�L��~��_8�^��e��������B��h����m�����~�Uh�������)�:�6Y���iG2��	�"M���w�|U�����S:�o9��w���C����')��������������~�w��?���K
�������T~�����(�/�#�����g�t��S��?Q����z����?�O��������?��O����������XK���*������C�C���������>���� ����~�������'�Q���!���������?�|g\��+_B�����*��o�"n8�
D�u��������������������_f8���������g����|)���3��,_��|�~�u4������<�wS��YFy�������C	�q��*~L���S�7���9�@W�gd��N�Y5z������=V*[�}��x�i��\�8 o�����_����_,�F
\�R��|1RN�Q���DU�!d�cJK�"d��)P4���Ib��3���S�n�	<,=��(OR����7<p�7�^��a����q�5�#l����>�+6����w����I���E��V�&�����j�8���@'!���x%�U�Z��m%���t��i*�����p�gh���`����$`�����#����do����
�]/v+~*�S��Kk1�\#	o������41��3��|�R�������	� K�I�ar�<�x�6������;C7��M�l����mSA�1������$�f����Z/��L�5E%��m��~;�&+�������{��	�[����eho%��Y�t��I�ar%���t>�����D��>F�n4k�^�����B���u���lp�A������@����rq�X|k�\#-��E���md[�
��R�U'����s#C{��h|���&���LGo��J,y
+�M�jK#[�P���@[�T���������F���4��V��=�(�@��UE%��,���#~9��$.��vv�����e9��~q�����3=8�.#'����x#gR��v ������I��hd'q���h��8�i^�=LG�2��o�+���9}&�~
t����AKA��UP������Vd��5��|v6b��@{�g'4|���&���G��J29��/�M�jS;�Z��D���)k�����$�&�����<:��@{�q/a5����q�$Bx��z��/66}$��Nl�j�l�(���\�3���@�*���p '!���x#�2�l�-�6�(��$��$hV4�3���@[��U�K��$d��G���&4X;T�}et�L<z�����@���m)8�Op_k;R�`mH����$	�Jl_1-�^�O�$T1��V�-(zt�T��P��m�,�M"kK�[��Q���`���
����W5j�N?�4nu��U_����*=}��+[v�P�U�5~��p^�SgbJ��%W��=�#���@���l�c���{)H�)|��D����GR��S�mtY$�!�MBfE�=P��,*�6�E���@'	����Nz$���.��=e��eN��������K�65|	����#�w����S<���.����@'!����@��{t�H�6�9�C
���$����S�;Pth���"V��D���{6�<���-1C���H�\���1/��M���"������}XxS��Uy�g�l��g���tz�\�7���Wh3m�.�M�fE;�}l�B[���x�/���g==E�x������[F[�:X��E/��i(�'k�Ew'����IZ��;��et[���5,�tz�\F�3!���h+���J�@7��-��\�����'����|��I��aR|k;
�Z����`B�za��u���f���Sh�����a�S��Uy��,���<t%K�NB���fN�������D�U�C�@7	���\��\�`8��������$`��G������-����C2t����8�e����:����,-���`.d��h_!�-N�3C'���e�8s;(�F[����>b���&1�����e�"�mQ�>��$�&��[�����{�Yk'�x��pb�d^�S�8��)W#:���=b������������G�$1�o�Q���m%�b����I��hjG�N�m0A�%w:I�<LQ���s��;�Nl)m����^����RPC��/H��}��@��
���J$��`����u"���@�NB����u��D�mV��5��ITmip�@A��Ol���`u���$�&���yy)�T;����$��t�^���k�S� �+��j}C�=��$���W�+A1�c%Nl_��>�p����.&��������6r)��F�'v��Y��V���J�
����I����)hxq��w���"���,�C�2���`g���}95sxc����wy_1�'���nyt���\�b�jp����\�bHGt
J�n"�6u��������F�C���'v����y���+h�������O�$Q��{_����9��������y�`�e^�S�8�vt$#�X�}h=����W�ON�
�'��>O����:	EL��[-*������m$��itbX�n8+��!��G`N�6
M��Q������������jE
���$�u����c���N�S`�j��5��g*
����v��������xP���I(brA=��T)	�WT'��tR-���&�����P��FQ�d��`x�$Q�0I>���<8L�@�j�t�A��D������6mj��8E������g�R��v/F1�������F��$1�6o�ZJ�T������@7	���(�J�^����Egs��I��a�z��-s3&��3���N2^��0O�z�{w�JBcr�h[��;[���(hgQm���x����:������$$1��eL�HB���
����"���nY������'�M\%�4��6@�NW��-oM���v�_�;�`�e^�S�:5CK�P�&�����oO.��������%�Z=��B���.�;	ML��[���@���m$�5E��O�&�����/A;x��{[�=M�$�0m=���#8���v���T�pCT��������!�%��������5i��|�YT�5�
�����|b'���e�0������m$�����nW[�>o�+gOl����b���NW���oO��+��*=��#��\���2��i�+k��U�����}��
9����vW�)~b;�tM��[�
v��\��r1]6U@��`�pG��F��$l���C��l��9Vu��[�����4�0\����=���|���$5��|@��_���\j�Dz8���
����<���`�~�1�F������:}�
�8����.&���J)th�������M�
�Itmi�GK�����mY1]�-X�wb�����������2R�>�?���nlg���A{g
vd���8~���������������w_����z����v���*�q�F~l�48�@������%����EEf'��:���5�H2�*Z��n d����n4+�HYk���m2ZJ�gt�������84[]��P}�a|�� }�/��^��^��b���O������
=^�v���r�@{Ke�+y���_G�������T���t��Q�nU[:���0�fh��r�%������b{6�>����	�[�kp)��B�
�&L��r|�������$���;<�:%nl�eh%����t���i`*��V�(�x��I��iZ���QzJE���:I�<LE�*X��dho��O����.��i(5i�5�@���5��do��
 C{K� �1S:	1L.���^��m%�Hr������l�)�V�-�v�����	,l�8���*���.v����(�>m*�b��l���vx]�����v5�����h_5�(}#=�$�0�o�Jt�|���F����2t��Y���.(��6!��
L�9I�<LK�3����.3���N�����L�����y��loC;
l����-��gho	m�G����6��e>*R|�_�������]��D�����S������8������$�&����)-�@P�}�xf;p�K�����>mj��Unb#�`;�4x>����w��������+C'����x#o2�\[����s(!�M�fE;[Q4����B�K8d2t��y��eh'�@�����p�^�]/ry;Och496����v�&��h���D��h_�>�z�x	:	EL.�G����+��d��=�T,�M�jKK;�z��v��*���"#'�������v�p^��U�)4�G�@��]wOlkkHxp��������^���\�������'��2���!��;	YL��[�J���X�m�RJ�������$pV4�S(���'�]�D�-<������Lat���/�'���Nj�u;'v�������L��2lm,��.�#���/��j^���
����:e�`7e�:��G�h���m���B'��&����Ii�h�MT�<9�(|@'�)���������h�+AH|>ut�0�;�ISgc�k,������km�����*7�rb�{�|��$�0�&oe�
��d3��-L	�I����G
�`�m2J�o�
v��y������\g,ho�-60�@������$�������d=���@����V�v�Q��;	EL.��Y��r[��,���Qr�������8s���{U���t��z�njG�
,�*��z<�$�RV���1���M���#�jj���$P�[���"��&��Y���^����NB���v���.���f�����t��Y��>zf����m4�$(�3r��y��X2n���'�����y�i���"�w�4���%=���(��wp`�hw9-�xT�����(��$$1��hGz����m���Gl��M"kO�[�
����L�t����$�&�����������I��%c�d^�S�8U���xw`{��]�^���O�F����^~�#^���������sN��L|G��g�&�����4�v"c��%1o	J�I��a�z���
^����eu��5�����W��(�����c����Cz\������>���E�E5���"&��#�I�y?��4w�
���$��4��;�s��6�����$�&�g����#%c;�����~b�e^���U����'hS���1R@���]�.�<��5zDgt��I�br��n��Vx�l�6�0����u�fI�[��x����vE%��U������0�[�����]X�z�)#���4���\����}
p�
t�^���U^�
�9�}�u�L�����NB���QV��7 ����R��&���M"kK\�!�`}��mWy�	z�^x�S��g.�;��R���Nlg����F�9v�P�^�S�����L��{K*��������Uh�kA�$�Y�'��� dI�?����5]�`sKX
V�m�Y��mD7|�&����i�>=�m�&&�A�vb'	��}����Wy0&�'��r���W#���"���4��A7W������L����
��
��x���k�N�$1�x�~�I�v<��d�v���Idm���$`�7+�Fw6GN��v���]|���M2:�v_���y$�9'v�P�E>��S3I�Frp��	�{D�(��������"-(S
��B��}P�����
��O���=Z����Wv)3v��Y�����.�vAc�;[�N4S��J��P�d��WTC&�/v��]<
�2�B��1Nlg���q�������P�:����Rh2��P�����z�A�I����6�(���9��D����%�@�=����"x���$�&��[��j&48���vV�y�	8��������>u*�e�[����'�����`�:�`{�t�o�(��*������<	QL�������1b�9���Q������w��h��FA��H� ;I�<La����L�����+��gJ�l?���/o�i*
�����<�}�o��[tyf�v���5��J'�sE�'nY)�IHbrI=��t$�*��6�O���)O�&������gh��mW�i[��+c'��������&Sa�vV���F_R�z���~�S���;H��&����}�m����Y����-��*=�����*&����!
�{ub���J�u�I�,j~+��
�Q���ub'	����a��#���7�_\�eO�z�{zCm�����(��}M� �z�9l������z�;�Hu��$d1��h�{����HH��o����$������rt3��mW���*�I��a}\�TX����N�t�X������Z����4��E������=f~���5��Z�����o��j]JC
�����4&W��%*2��n#���d�����$tV���0�Xy�n8G�7����
������:�����}R1>�-�����Y�[�|CbG��R���?���?��w8��o��|���?��{�����	U
e�,�.������
����Km�)��W�tv�<��(�M_H��YZ��$���:��D���$ypQ��lQGS��D��� 7Bx0L��������b������6ejj����>�3��������[�[t-uFv���XV����*��S�Hx��V���D*�0����4K���:h+<����N2S���{IN����[>;�������8����8�/#{������t�
����xv�r(�Ihar�<�nT�X�T������0��$����������@������@'�����	LlxN��xLx�;�@�c�5T�4>qzl{���@���h�z�vW�W������$�0�ofe+���@[	#���&A����n;��/�fZ�XK'�$!�0-=�������-��kBt���E@Me�� �Gf�������@{{*��nh_�[MAYV�����"z����Q��`���Z�������E��@[���/�v����D�pC[������}�x�L�e�za������f�7	>�����,���[�;	gho5n���$�0�o�OjZ�W�m#�4I�t�n4kZ�'���.���I��aJz���~s�6,���8�2t����>M%���b0�Nl_C[�3
������!�1�WB��D���� &����G��
��Gh+�
�����U[Z�����b�UD��U��I"�a2|���BP������$
��Y���1oj��;\��=��������-I./��z<}�_��$1�o�Or�m#�R�;pgc�n4+Z��0�;�m2Vy��,�IB�azz��mH���>b��et
I����\/vw�=����#����5�#9�h���@��7
���RZJr+s@'�����(R
2�'��t��D{�
v������?�C_pl������Z����w��er�0����`�����J��H
v�P�U����L�����W���)���]Gp[�v�XI���U�}/J��Z�Y���5]v�H:tf@�6Rh��Gl��MgE�]j�`i[�6
�\��.
.�I�fw����I^��q@;kv�����K����w�4�})(�D��������@k�
��l>�`���v��t'��I�ar�<���y�=x�-�F����I�A2v����M��"���`��M}KS������{:7=w>�+>hg]~<�A�,�������M��mH��)h���|�U��u�&
���Y�k�,�;�������/���U�'���F��n8K�������f�:hp%���$h���
�NBz��
������e�z��;r�A������
���e�I���`����������;	EL.�Z�!���������3v�����vd5Z�Z����Z`r@'���	��F�!��[��u�!o�~��^����������w����OLW�lwu�"8��vW�vb'����y+�2������H$RZ��W����f�#�����mvg�B�;I�<L]��-�\�v���LG"���"���4��6�Fu���kzKI������'���6�KU���"&����@V��������'v�������~Ka��5�P��;IT=L��7�-����v�������]/�yAO}�Ty��4��[����${�!A;ksCN��Olw�|��Ibre����1�.s�6���j�Vo6K��I,G�U�`�)k	�#)�I��a�z����(���`;j��`/�]/~y�NkA(�
��$�7pg�[�
hY�v�6�����Z�]��P���z�9I��q����yQ(�
v�������F�f��[[�;IT=L������{
��>��h��^���k�S�(�<���e���}�$	�:�}��d4X�vb�*���N��r'v��\�7r1U����m#�Tz�=�������=:�+C�%����N!��w�0�U�������:(Z{bg����;5+wx����{p��pb�:����`)����
�(����o���^�H���(O�&�lW��h
}#|�y�&��X���$����e$P��7�-%���p-�Ib����H��W�o��.�HT�=��3/��M�
s;:�h����{@��cqOlo����`Np��DN�"�'x��\�7�-(T���F���x@����7	�}x�)��o;��B'�����\;I�<Lk+C���BG���;}*~8��E1��i)�Ih��=�}mq�I;���@�~�JX�\F��-�#��n���(&���K�I	p����
�=j\��M�kOS�����*�������NY���-qeI�oOlg��?T��XN�z����jS��(�{
��M:����Cc��'��^�2�����#��Z�����Z��iA^�����\J������I�,i�G&����Jg{�p��I�aJ{�!����:��%v���P�}���b���V zMN�"�
����i����'%I�@C�w��R�G���Ihbr�=��t��@7P��V\���[��Itmj�Rl�}���x'���$�&����:��������4#tW�	^/�ymP}�T��V�t���o��G��4Z}qb�+v��%o���]R�X�����*&W��LqG*�c|�� 74,�x��Y��d
��y�[��D��('x��y��g�;�-Q:���v~s����^���Q�\i%�r|����y��1�2��[jk�}�p�{K����*y`'�����8s<P���������d�&���9�|Q��
�N�t�D�NY������G��Np�C��"��g^4��{��8�-	}Cw>��d��U'��n7$l�po��I:p��x��\�73�s:�����J��n:KZ�V�S����(p���&��[�����t�H���Mo���(�~��^����"Q-����~�w���#i�.��Yn������;�mu�VG�X�����r{���s�!j!��F��rmx�7��=�r���
��MO��-����Y��������wJ�Iu�x���
U9'����=��r�GF������?���?���8��o��|���?��wi��w^�Ti��Bg�h����x���|���b��eh��
	
NG��M������G���H)�CK�3r��Y�}�����V�b-L�:I������O�mAy�_��G�UO��7M����}"fhg_�$<��v�p��+��g�$��NB�K�a~+���%�fZ�O��$������}��Lhkp�\�NS���l���a��}z����M�1��bUG�V���N:�u�R�R�����v��
���@'����x3�1H��Q��E�(���u�fM����s�*d��(
f�$!�0=�l=������[@[tGF���I��T��������
���|`�5���3�@{h)�*���&��l���
���������1�
\����bJ�.`�NS��X���������C�������M�_l(DpHK�������������@{�qJ�9	9L��y�����l#������T�I��hd��_�w2�U��V^�$!�0=��V$X/T�}�s�L�N-;��E/�j*�dz*8���`���������������_���Q��,C'�����(�1�p<�U2��d�N��$��4�Y�D>��"
m�>�����$�p#[��`�F�������3������q�����]l�f�+x@{kq���W�k2��y '����x#oR��`�hY����b���$hV����v�h��A#W.�|-=���.����:�]t���������<Xkyb��y*��w@�~�x�>�}E�!������ &����GCF�]d�F4�$�@�T��D����!%@�@����y4C'���	���v��%8�@�*r�G��������.�'��U�$<�Vw�;����m
.���:�$z�9�}����f�_;	YL��[M��8���6RI����0���$pV4��/��m4ms/�NA�w���3e�_����+���V�o?i�L8W(g�u3��R8�e���a�d$x����5	�^5
����mY-�M��>�4�"��Q��T�&����'v����k���k�Nl���0�\���$��o����0t����Z �;������j�6q��lo"����}����dgunIk�}y��67%:�`'����y+�T���
U�����m�&a���X�b��jr.�I��a�z��.�*t�n�v���B�������rZ
BG^��'�������@�j����,�-y��
v��\P3%�z���S��wD���&�����HZ�gOl����+��*�I��a�|
�[�����^���`�e^�S�85����D�Gl�2���@v��8p�[�v��.7���P��������oiHF�l)�vu&����I�,iw+�-�+�f�:���'v��y��XTn%�-lwAmZ`W��E0��i*
�'k�b������\��w ;KjO��6Q�v��L0��.�I(brQ=��[h�D)�F�)���M��M"kO�[����`����D[�
v��z�,ox�\0
�$m���O�sEJ��
�^(������q�E�X�{@Nl���(h���w����]�K�G�`'!��z;���qNl3n5�y�I�,i{'�,�f��m������;I�<L_�����J6�A����Z�������)#������6��h%p�5�s���h^w�~�4��F����o~kt�I�NB�K�a&����AU��DTnFsv������$�^�mv���Z��D����x�[S�g<lg�nR�����^(�z����1\`�'���-)t~O�v����Bkc��F�A�4;	QL��[��6���.��m&��A}���$p���9���f�6C��Z�������0��T�o2�7� ��QAt���
=�E���S�`U�7t_�[IR����[��*��WZ��[�Q������z�I����-���J������nW[Z�G�<�u}b����A���v�+��3����z`��h_��E��@.���z�O��^�3��iN�>�}�t���
B'����}�,8��0���RG��x�7�q���"�r@����F6��Y��i�I�,����m�=��BG��`�M�N8�;����(�0,$Ol_�>T�*�@��S���PFOQ��!'����,E�1?��v��&�s�
�YF�Ou����NB�K�ae�2?i��+�F2)����{�
v����c������J�;�
>JO[�K��|��~}�	������;����]/�y�Om��ToC_�]�����l4;��U��`����{�tGV:��<	UL����DLN����VR<�C@o�'x��Y���3�-�Pr�[i�X��&~��fJ�����QS��u����l)y��O�zQ�{{�D���X�V�����������[f;�\�Y��E�M��z�;	IL.������#�`[)���;t��	�$������6���V�����O[�����\T���'��V���rv�����>y*gm�,=�}D���'��[�����d�,,��Z]��u ���I�br��li�&��U�H2=U�\��I�,i�+����kR
�����z�<������8;<�������-�����N
x���>M������7p_;<�U=XiUF�6�#	�M69��+NrE?�Z�OB��a������=����%�`5W��D��fx$/`��[������MA~4-vJq��@o�;�����Y��O5p��]/�yMP}�TU��@���Oo�����$8\�@{����E�����)��V�7�$T1�Zo�LQ������F�)�����t�&����
:���+k�}�s�'	����av�4�T@���_dKtL�x�(�M?�E��1�5 ~Cw6�#Y�]����������_l�u�:	YL.����Z��^�n������:��It�i�+O}$��[Y�1o�@�R��������x$����[�RxS{���J��n�QRG�����=e�7��������*.���=}���:��+���I��>���������
�It���u
t�x���v�l������R��kH�*����w��5*��E�}��'��9��h�v������O���������_������?��W���oR�����?ee\�����w����\��������F�������~���3�������Q�_�G����?�o{���/�~�\���'J��'�?�#?�n��#?M���/_���|?C���[���������O�<��O�%}��p���������������%�>����/�����������_��/?|��������?���_�8����O����=?�+|v��4����j,�?���������]�L^�Xi��_����*	��ed��N�+����[x�����������������_�E�������Q������?��_�~��~��+��Q� ������|�;O��W�N�:��{��) E�Kg|���R
�����pv�Et��}�p����\f�����C���MZ��h�_��_����/g��AF��6���Y�b�%�����&��F��z������d>8�:����OZ�/[h��2��J���Q�u��i�����2W�J�WAY�^�1{��:Y�%��`�s�)���8�7�����u:���,����{��A��25g����G���6��9��2��yB�6�vBU3�����-�����[�Q������E0���zA�W.�;��]p�I���H�����]A��@�lx����]:�3��k��7XF{���\8���x/�N7�W-���:����s�Y����G�F�=q�u�L���LJq�u�/'NHG7�z��z�d}?qY�K�,�f�4:(��6J�p9m�s2]T0���� wO���i��rVk�}e���6���%mf1@-���7�xU�F�cz�������d��� z2��x�j�� �w�Y+����d 1�c�w\�Q=���_�����v@{W��<jLdh�����F3���3���_����U������CT��'kI�[���O7���&�������p�+��R�����Ge��S�(�ZN��N~�����c�h������+)
�|L�!�����sd��J����K	��z�\�U�� �MvmZI`:�����-u��%|P^���
�&�|����
d9���J�'�NoD&�:N����rn�;��-��?(�W�J'���W��<��t�:��d6F�tt���V���9�p[0��F���������&�������������r��T�Ez��2�SA�4J�d��j>�Q	��y���Y��h�����[M��{��<_y��Z"��M�H)��?}�����xm�|�
�}S���,l>���������mu���������GoO�g<��QT��R�(%�
�ZQ����i����A���~tfh��q�S'�w.*��z5	���	w����_K\L��h��*�N����[������v�;6tV��z;��RJ�T~����v��yO��,�UAI#��3�e ���W77����Ps���#���wy��[��-�MuN@��������	(�����Q%>��V��k�(�!���x��Z��<f��Pb�n<������<��O��������\f�-/�]�q@[�w���JF�������nv%^�)�����:*�4Q�W�_u��m�k��EB�6\�6�-%���*�����\��nn�K��N)������5E}���������`%LP]��w{��w{U����xR:�C�X��Y����������&�SF�����v{_A��+�#�9��,(��U���B���I����7�&Co������������S.����<�3�w�G����h[���������W��r7+����5]���W�u.?k(B���Vw}=s�v^��cI�Z��n����eQ�r���"��)uB�����;�uRg��t�|9�����\�^�:�������a��-�#u�-`�?t��2H���5���0�x���JT���x�����b/LL��:�d�����$|��,�����QTQ����)Pm�n���mT]g5�����-5Pd�����p��Y�7���P�'���.RH���A$mS��3�����8}�d�&]�����p���r�/�����	�� �`�iM_����'7��u!�����.=���SR	%b���j���oz{��g�t.�C�.�W�^~8pn������Z�
�*��'�y��Z[W7E��MO�p9����[�y����3��4��0?ax��"������X��xRZ�Gi�Y�����O���u���=)��&�A���`�Wyv��_����P�"�X����Nl���u�H��k.hr�`�s��2t������11�Z��O���`�?s�tq0yj�P��
�>�UXu�/����A�P^G�s_]�FS��]���f�A���H-�F�-?�;�r1]�KW��
�$oZ�(�'�NrNY�t]M���C�c��Y�� ?��;F�����gfHwko��U���g�=�v9����
�L�����	b�):�F������hgG@�O<�DR�N�U�4��T��Z�h�2����j��k�{�~n��T�h�"'�	�[n)iULuh�oPJ���8�7���+P�)��i/B9i��ou��Q�;���M
�3����!F���[s���Y)�?��V��D�N9����%�N����f�5Ml�*�V����
^F/����W�����M���/���Aj�����*�)� ���+�Agl��r��K�l,��_���0��m7w�����1�r�cRk�^����C��K���������Kz
T���h��y\Z����E'j�Q�g�i9����PV�`�m����WO�~�U��FBg'��{����'�w�C�+���sd"�����F��1T�7����QJ:��a3����Q�L�{K�&iu��IZ)�l�r�
�j�B�rO	[9��+�7��uz�t��8�	�Y���f�C$���S��V/'��I������K��������rGI�����j��J��K��X�(x�^S��2������I7��?2��z}��6#.��v>8-��Q��)���`�N~D���|5}���7���l6y=[�#"F����f�jn��iO����5��Qx/�����v�)���������U����*�~�*fl�zG.�=���S<�����^]�M��4��8-B�Zz��1N��G���������*���5��9���?}����������A�z������I�	���~�/fW��t��T�]YK����(���]NJ'��F
Su;s���0���������m�[���K*��#�C��7%��n#�rn�'��H^���o��������V��k�����������t
���r��"s�����9�U%m�s�*���~:����z�o����������w=A0`/G�v����`o��Kk��k��etTVF+r��������=�]�8�S��(�����w�uW�i9����K���]��Ls5W7HN�~grs�YM��}���n7�r�X#������������MoF������<x��<��F��n7S;�R����U�N��:6���h����M��I���:�r���
y?��5�O��#[c������
���ho? ^�b)���!����T��Q��,���B{���6S�kI��-M��U��e
[�U�|������Q
�o��'i�/�@v���I|��9��e�/��K�ce��k/�T�H�j���^O^9���QDU�z�+���q�D�r�/���d�S>de)cU��vH�s���� ���^�1l���V^��t��:�0��F��F.��_�.-��"�hs�w�^����KSy�z5X�����m�]�zvy�]pB���53��'sSv����� ('�}D��2$cHp�)o#;x
D��b��l.X}=����/\�&R�jJ+������_JgelzZF�c��^��������a�lB#:Vak{�j_�O�,����|~E�1o�v��+����5{��({-���[�)��Q2��$��P�K���+���2~��J����F�\�UA�J���)����O�;���W�������t�b�A�,�V�{=�c�;�}�%�5������r�(���J�����!Gz������F�����2=�j��u�:�J%��ydi�y�H��,8*���8G�"����U���5�;��*
"e�mw���3��,I4z+eR�����w�8��{��_������Um�R����SfqU�f(�5�OeK^�~}��6���\�}���0���fz�!�^���^�h(���8��q:I�����[Fy���2�4��E����E�w��K���K�ndFE)^
�|��%O6����
�z�Q�~��V����R��1H/m�5���;w���%���e��{�#�NsC�,6�#B���m�[N\���P�Fm����j{�Mn��sKZ�t��b�U;,�ir�a���R)��gqO�s����'���ma�
e�+I������FN^Ng-��2�������\�i��c@�vj��+�+�
�o��G��w�eO��][�
�wA�r\��t�����Bar����9`H�$s>s�����G����]M���x�/��?�@���u*�*V�k y_�{5|i
�K)�V7}=ylv���B*�b�{`?�Qu�k���J'���V�p�����$��������3C[����s���q^*�C��g����PN-u�
�����U�����d����Q2�����������0�9���e�H������_�b�
>����JGf���f��Z��]��;���3���
��� P4����]�AV�-3�_���JM��0�E�{�����r�7ku�/�������![5s�����T���>���:8�mH���Z2�Ios���� �V�k`����"�\�J��~4�����{����e��OAy-dU�R�M\v0}n�#�Ol��
����l0.�\]v������ZvY����$�
������w0�(��jC��������N���BD�j�jR�t���_Jg#�U��DH�fV�4����)���d�]��m��7��+�����{H(={�����k5(A1���O�
V�}e5�Ri�/L���D�.H��45�S�B��"?u|��Go����
�������?~�S�:h���RW]��e����j�&5���`���mt���3���4R)}R#�G^��\�z���5g?�x���Lm'��I^��^�:yh��Q������`'�7QU�������Z�8/�������F�}�Ss���"2�Y0�!Pt�����%��3�,�p*D�J���D�g�I9��D2<`�Z��4���BR�VR����z���{�c����$*������g_�'Ol�o0Z���p�_x�
��{�L�2Oa$M��{%u~�d�@@o�Z~s��y>���������io�Q-��r��.7vX���WSP�=�NZ�Z�i
DHO}6���Vw/{9��.��e
��ER��:J��/��1"QZ:�S�f�V�E/��E?�m1,��W�p�N��*������G�����*a�����r�s+?y�Uy;TM�y�����)_��`k����b;�Olo�_Oh�RB�d��&��koK9��|����AkQ�0e�i���*����
�����HF�u���=��������C���/b
�`o0�K���z�Si��g��G�j�%��P���x��1��S:X����l���pO�j9��������n�R��T�?g%o�D�F�n���������/�O:i��b�A���������:���_J��Z
�DUUk��"�I5���*y�I��6J� �?{T�M8���������H���4����lvy�S|�����G�[��z�`T�C0�^���u����@���>:�K��vFV��I���M�A^�3��W0����G��������p����@�����*�~m�l�����J����v.<p��.�9�7���+JUg�0�&]�N�h��V�w�|�S�C����[m�����m6R�I������'a�C"umu�^��d�~"�wrS`��Q�H,�:��.>�z~I��1)�dT�������w�T��R~�������kZ��6��6��� Pz\��B���-s=�L�*����f%�5�=����2��'a����,��hp����L��eA�� how�^OgaC�J!��5�?tZu�k�J������B���u"���j���akw�j��S��i�����g����vv,�]�
�w!�tJme�0�F�(�����/��4�{�-|���m����x.��#������_�O�k���=�k�us��G~�:��Ek�O ��4������O�9�}�AE[���s��=��/������������,=�
gj�����b\aO��m�<�z���/Fi���&G��<&]��I�p9y�Q9isW����r��4��0{`X���Sa[ c{��A��6����<�����>��_�b��U��
���^����)��n:�ak_��V��
d�^�7p��G
|�_��}��x�wP��?����Xj*��!4�
c�s.	[�NZ���O�^�<�P��}YG����.�TV��K���:�!:��)���F0�CWf�����>��V����3c�~���>���L��&���W�Rz����>���k����4/S�v�D'��P�*����W^F��ZY]�m)����]�rvi�\����D
5�m>�q?��dka|��&k�&����1D}�i�W)/��<_�V=���g:P9���VNJ��R[Sb(�����Ucf2(.>����"�j��o�������{)k�2l_�!�$D�����A�4J�e�0SQ$:��=&I^1���M��O�>XaR�z);�����VU���?}�uU�V������2e)����jU~����L�����O��V"�rDJ��w���_��t������i
RiR����-�v�;�U��;�F}����}"q��N�i!F	S��wM".���9"�k�L���y���J�G������� �Jwk-l��Uynz��y�d��k�U�{o��2��Z����}���!&�B_�C�-?���G_�lgg���&�H���
���@)���������1������k��gNoLRh�	��15�n��S[]N������	���j]�4\�p1���#�d�)�`[���'��*�TRMB��j��/{�v����Y�U�$��yG*�m��-+|L���>���K
����Q���9H)_u:��^��n�����+��2���������S�Mv��� x�pS_�v6���Hi'���?{o�4��c������X{a���/_�tg4�
kFR������������d��U]Z��G�n�*�����A11��'H���i�2}�'����#�C�������C'��p����BCE��%�����w}��{8��uM��nh��Z� _U�����������T�H���I����r�(��2G�1�������WxbB��7(��z���A	�������:p�����-"�#��*:L4�:��� ���WxJ%kH���4g�����%dYkU��Z�n*[���7q*�y�AP��9[�F��&����&N�D�f%�^��b�xS=Y�"�o�����Y�'��s��7C_��*��#�!�#��A���2xn� ���`��uM��5i�%�X�����;M���~�f��SE��N`
�:k �*9��3�WcbY0NJ��Ur[�Z�PF%��R�?�5��>
|�6<������^{�n�lv���qz�'�*������#����&����1&M��Gfs�L��9�@@��C�;wo�����)%���s����&�I�IW2��k��W 6Gs�|����3k.�
D���)���Uq���<��$� f������NN�&��\�:V�j��Y3+`�cA�|����L���h����
lG�����\n~�_?��������X~������?������y�S�}��sO��H4�>��ztz�������@"Y�5�.�:��U��B��cx�V��/����)���4�e<�vd[LT+��5�n��v��������	IKs��Kh2&:
�~r�e�C��4��w�@���� ����E�E��������f0���:i@^��fp��������G/|Z�n��
�)m��V�B�B�OI2I��>@���(]3��
���x
�!��������-�h�h������E�5��$7�%�1��o��&�en[D��%�G�=�(:����b�l�k&��kX=V����6<�=���{R�;���:�(Oa�LG��^��?���Y�7������i�-twr��>3�OC�Cif~�|]{�n�a�5�YV���&�HLk�!�--t�N�7 �J���� �5��,���V*��i����Y�
��tNM|����.b4X%D�%�����A�+�&_�4si�t�VI��/)�#���cz��L��t����'���4����U�Q��$��&����3���,��1����xD8��Vt��xj�!r����w�������~5��PC�h�������@3�:���La�E����5���5�ZVx���M�+�w�"�%2�H9����hd��7��f��$����������'����5���]���������Y��������rl@��	+��Fn�����S���G��:��V��	�$��ku<�hRO�y��#��(,Y#z�q_��E|�=D;n�&�[����8���P;i3�$�%�T#l�sF2��k	Wa>��n����+���B$�}H�D-A��~�y43PK�|��N`�*3����r����f�z��eQ�&@�����������Q���"��9�T|1W�!Y\;}���i��	��[�C��}Y%�(�T&I|��F���Ums�0t�NT3��:�@|Q�c�5t7|�
��\3���!r����r��|�el�+,�/������������z8��[�LP����E��|��4�v���a�%i�U�n'��1;�^3��&"����v����-CX{���Ez,������ux/���	d���
���c��;�LP"��5�Y����I���wCV���M�D�����h<�v��m��x+'!7<��Vc��
=����3{O9z�n��n�InB3l�)���lCP��Ts)MF�tn��
�z���x��0� �!)�c@E�����������V3��-
w=N�U<Q�q2���'l3�����
f)��W��)���mh[�A�����3*�S��/!��o&��C��-F��r��}���h~`��~�����i^�hZ��+"�*g����#BK����B6�o��f3�C�T�`�+-B�x��B���iTxuX�Z�v���q�'#�q��J�k�^=��Us�U�^���icM6a0�0�vH�����y����>_kN���Uh7t�tq���o'��|�4y4�ti1�
X��)[o�K����/y����m�c���5�h��h��xU-��X"���.�m�t�9[Y/^n#��=08%�Czo<<7�2D�Tj'����`�N��y4���������N``�iXD�4`��sAf��0s�I���G�
�_q#KB�z������yd�m�(�Y�^�%����S���2������Z� q�jk@H�d��d
���Uc�--�iT��&v���n������������1�����b���������������i�������Q�n@1f���9��]��4�X��� 24�7B9�	b�GDqpld�E���"n��}������c������5���/��)���N`p�!X��4`���Z����U���#S�d9�)�R�W�
���#�����&L��#^�,9$���/�`�������������@�e�0#b �ZbC\��Z!l�*"��X���^�N�/����]C��W�[��&��5c+��N�|R�8�ri�b���K*��}c���(�Z���B��
lA�������eu��mD�$#���x+�����AV/��A���pxn�!r�O���vd�|l��S������cYhW��3�k���Vy��h0��wL�����8ad��T'Ln��ZC��R�"w h���HH��_���mQ����M)>������^��!����*�bwk{h�D��P��5G.J�4��B�������=cL������GT&\x�Ni���������l��=%�a�Y]F��a-�2������^FDz�y
�i���I�z�tR}K���o���r�?n�jC�����G�����,���o������c�`9�����
��'IFZ��J��H��+��$���k��E^m��WV-vAQ�	�E�Y�k��yX��}�Fl��N��8��ec���Z	�b�I���(WG��Zk��*BO�%���zRQ����6�K�n��������.���k3�d����8��r-�y��B�#��~������EXB���������+7s�����Q���Q�:�/��KEr�qh^��������@o|�.�&)���k��,�m�4+���b��iZ�b��F����|� t�Ntj��.���{j�!(7(�_��clb������.��S
�v��Xb'P
�J5����Q�A�Kr��[���&Q
���RMZ3!J�j���c��^P��A6�DVu�$�)�NN6�u��z��Tv�3l���"U��Ia�����N����s��;4��"�os3�>����T{c_Cw����FVj��?�Q�I�8x<y0�����M��i��~c$�Gi �5������
0����3*��(kdc��N:�b��dP���.{���w=�O������QO�
��[M�*������+D��Z�����4S���;��uv`�y��{M)���e��qJ�{�{e���w�Z��
�!*��M����S���
��������u�I�4�;'�>��T{�s_Cw��xa^0�����FX�]FbGX��gO�������� �)�����R����%v�L��`�R)r�'m�$|�8�H���B����$���_����RK��������
�a�dn���_��{g`�d��� ��L-�����!)?�h����O>�24�A����",��&XS�m����
�;�l��	t���
����k(SV��Hb���y+;�����
��"R��75�#��1�E6���
H�j.j���%�i����m�O������5v�v�6��
\�v/��
�%�&����8�P��7m�	���&8�������T������$o������)�k��{PN:���< *�}
ht������c��?_����2�%�h�@`�fh���.�*���on�v��4�� �3`��Hd�|��p7/�^����
B��k�������	U:�b���S���d�S-vUPW�Y��2�����^e������+��k���Q�K3@�*���Y����/7��{����N���(��T����o���P����
kuS;uD��?�N��$4�1����E�G�/i��	�V��A�
_e��U���7a�U����������W�����J�����^�#�YiB�Z���� ��p��7@�,������{��l����n���l
�kV�����w�B7!�|������Z���{�8�
�U�����b��T�;�q�u�aQb��)c����z�C54Yg�;��,TIcw	 �$O+Y�������B�f�R�(�
��pN�8�w�j�9�w�����
�j��`A��'q�/a�-���gOX�,]���}�%|0��������
@u�����e�s�pdi_������+kH��Z��������J��N�,�R�� @!z:���Ukc��<F<�iw��%N����v]����+Nh��:'L^C�
��L���	��J�"��C�\D�U0_&#�j%��:gS�����
�D.2����X������*��du�[���fi?9U��G�y���������
)l��Z|u(��0���cFb��a��/�ps��tr���fN�����������t{� � �����D8r���'��)�#�WAR_#"|����ey�q���k�����P+��GhR9�<���t�J�M��2�T��� yG����������CD@��L���/���h���������k���;�y�u�a�d���LF_Y07y>��2���;P�{}��?!�)&�5�p�MMi<��n��U5{���=�6������'��!+$�����[��[ �������:��6���w<!�\�Bj�w���O���T)���v�=�Z�n�M*�m�"rT�t���?��|C(��Qq@w-#O�c<q�a*H�j|��-�p�@7��Hb�r�5W��zr����,��BH�)��&��h�~|A?a���A��%��
w2��E�V���s���k���"z�,��E.�8\�U������r���!b���4t/��>�O��V��dV���J����h�h�5t��?��]�qZ�',�����f���+�M CU)����D@����ZU��6
��5�����w��dO�X��O������j���"��XE����'Qw5�C����L���Ba��r��Y0��9-����ln�����)�
�P��k�*YE����)Qa�	���3�5��QM	���(���f�*���mQ"('���#���&�%�z}�T/����1����;���U��.��:�u�X��!���D����I��p��,k�!"����w���?
�_jm�k��"�8�RS=������N����M�]�k�n��#������V�6"&�@I')�+�C��x[&��$�!����izO�h���H;5�K�n*��MH�r�[��(��KL��H0!q�B!C��Tc?$_q�"��;*�s�����/{M�I������q�^z��6=�,���Z�r
m��w�!N��_�IU<5!ih�S�N��{�B.���������T�����N �:!��>����x��F�C�;���m�6:kK�fT�R��C3��f��i }�f�u�����H7�w����yR����}f�����Q�A�X[�dY,D�a��<`��cL��a+�Z#���Mx}J
�y+�}������-L4�"�(yZ3yYiNt���=|a�6G�Q��=��yR��	��	 *%��a�_�����uUh3������V�6���8�IDQK���C����N_pJP��Jt��xj�!4R�-I�uhY��MQ�X���/�����������5v�����-�Op�iF��6�*F��!
�k������UP3�����9�4���?
��%[�6CE����%[��~������^����y����z�q1���^$���G+�6s�`@���ZK�k��:<P����~*�^���zpz�����s����3c������hfaGO�F��D*�o�0�(���M�n���	Z��<��(#�H�\��*I�$�*I��e��	�d�P�@�����rh�D�<���	B�M���N}��\c&h�V�#-��c��v����)~	�@�:M�H-m��5g��,j���G��g-��/mx^m���B��
L�e�T4�?�F�B��im��m��f���i�;;�D�N�_�����z!�)�v��F.�Ns�7���$L�L������g��s��
O�����&V[�L���"��5�$�$�q�v��e�\��]��=�){�g�>\�`�Gq��������;�Ph�D&u�
�&�%�$����m��)�6O(t
�(�������������&�������	���R����I���7�;�x�u�a�d��}
���J"����xV��!��=��3�B/C'IJm������?��!G��/��ih����J�\������!@�����]��
�<������/]D�4����D��OM+�������/�;�s���*^�Cs�����h�@d`��J^3���`X���c
>J�`�tM��5d/�S�a�N��M�+!��B���-�"��4��Y"G�?j��9t���)��H���Y���?_c�)h���<�[b�5��i��N`
�S��*�l?��6�P	�)r���mS�)�E������1"enC�g��i _�SUh�f�����eq?9Q�a���2v�.��uB�b(mTb�U������%�A�9���S��:o`��������J�(�5�M�F����<v7��
���f�W��"|������?�0��E�rK?B�a����oU�@I���k���o�6s�Ei>�J�];�� ��T�R��"�/�����������;�<*H�8�9�K��I*{�^;��k���CM�L�����N�l�{�e����TM��P��IY�������#����g]c]��4���Es��96�����,�����Y��h}m�L������q�6���9���p�9*�%�A!9��+��[�k����B�R��}����3S��Z��z@4�9���
���v�!��9~�1���D
��:GN.�����h(E/�#P�Y�#�e5,��AP�<�N")��1����YR�NS����U+�6�U:�����~������_���hy�3��,��_%[� �6(m�b`�Aox��������ZS7cYsb��������?
��/�Y�S���5��~?���<�`Aqm���
����l��� X
�k��1K�y�c�2�
Y����,{J��������k�n�-����F��7��m�a���x��z�����<��Wy�������G@�@-�p�=���"�Y�&m�o��P|���9���I����g ������x��*w���=��4M�C���e�=����K'y�%x	�$�������]|�DL�fp��w�du/��>�oN��E�z!�����,���?
��K}����'a1�tHp��tH*]Lm��\
���5A�
D���k-P�]�D�1!�Rs�N(r�����E��^�\R��������aH6a(6���k�+'���eCY���R�����y��8c�����L��Jo���y	�6�$�7�����
��ZVd3D+R��JW��Yg^����p;M�Z�,�nc�����\n~�_?�������Y~�����?����;�j���E_����3C���Y�\B��p���~�z���P�9�5to�����V�'���`���kR5������T!�����^����&2�!_����1��
].U2D<�1�ip��i��:Znfkz�w�X���q��\.1�5BI�+}��?�0H"Y�@�,���"wW��A�SJ7 i��p�"�B�qta�`�$
e�Dk�Tu�u+���*�5K��Y��O�)Ox��U�p�$^S(��GM� �����,52��r�:�a�X�p�"��GvK_��4B�����S�!��Th���;������5��$=�4qg�p���0�*���0Lwj�*��m�V�Z��v�����2W��e���
�iN`�k�����l��y�aBN�f�=����f�.-t���T����!��Y��? _�44)�p�����j�Y���_�:������������
{F�44���2nn�� ���}�|��z�@|���@���5�pf���X�M��l�f�w�@��&���L��c��R��t��yc���^t�xjj &@��n�w?�{���������i]�B��-t1P���Eh��:�YA�����@g������p8���Va��k�`�;/8�����[�����7f9b�Y�^����I��0w^�������8���fB[k>���In�����z���?����������q���_=K�]�k�n�����vH	53p��M0I��u_|�z3xO����+�N��<�p�A�nR���1�8��f��kS

��irX�qH���Z����`�G��p�9���9�������iUy
=\��q
=�i�~���0
��4�"���R���B�e,+B�L:���]pAh��*e��=I$J�Ht$��$^+P��:]��%
Kto��S
�I��.��_C=t�N�`|���b�J��!��iC-��f"�C�T���x<�Q���W�V����������[�Ojd��y]0��p=E2�6���9z��4�o�'�8	���{���M��1 "������[�������L�f`��=�L�H=9=��OM��Y�
�.���e���)����=���@.�Y'�%
���J�-,�������S/����-�W��q���Z��Y�is�i����oW,�������v����g���<�A;��/���?</��
X��&��B�Y����4[V_�����|u{�'�0����%����vZ�&�4���&	�I�������lVoL"��y�g��7�/|���wN�����i��dT� ����$�.���CM�?&���)t�fI��R��C��/%�o�i*�Y_CJ����!��M4tz!\C'
�N4,�K�[�6!Cf�R"�(O�{���w@RKjT��VBg��d4���cX���\�#�����8��c*(e�^������������&V�N���������+�RJH`���y����K�M��6?��o�A��
{���K#s�D�l��]v,�A���x��?"���>H�l)��%�(��;�Z^C�&
l�V�B��&,
 Y�:R��C2
�9���#c���qO���%��[
�M��Z���9/���0�C�s5j��~%A^��W�]C'�u�(���x%6��
Z��"��&��t#sK��[�6�#���9I����@�MgM���pQ���9�iV���uV�\�:��k�nMmX!���\������`��B���}���?�� _�O��<5�q��5F��Pc-���]�+W+�� r��8�2�.og�]�Z 4
	�.�x���8�W"8{7�z8�� �u��#�*sh�,��{�7@��r6���Y��B�S":8�����s�
����h�
����%���|#���J��c�N�����:�k�u�aQ]��E`#@V+��O�����?:���	��=�0��CYT����yPm$�X��;z
~�|`;�ze���w�c����
�7�,�:MC3���I��q�`�`,RJ��'�=%�������@}7������n@�V�}r5��,b*����}�8z����9'F�$=���#
>J�P��&��m������I����Cc
4�,1��9[����|���m�GK��/*���� xn� d��%�����c�����U~�����_I�4v���x��*M����,cr�(���5�I(���6���a�@ip�N�S�Ds�"����tLLI�#wc�����R;9O�QG��S�N&a���b��1����V"����0�C,��JF�����L��bO�Ih��^i�5v7��&�U���_��|C�d`��a�=�ZJh�D�wUK�a�54��>\����u�5�h��i#��H�h�b����M�S�\��]�^��a��n�B�X8�Y�+��{ehC������/3-��5�us��L5U�t�G\cg�{�Nw,
P�z�����M��2��MZ�����
s)Pr�����}���z����4����	���0����$O��=�,���^��k�n��B�6���1o�#�w�����T.m��g�h@��O3��t�^��kZ���@=t7��8|��w4P���k��k7|x|d>�*k&��D.�;��x��D�!����B@R�d".��:�X(m���b"'LPR�U2�8`�6�v��+���~]Ns�p/	Dg[��l@���i�J7�[�����2j��z
=�
��L����o�s���|j�xY,
��R@q�������/���M��T2�q^%�hTG�����;*���������id�U���X����[�c����_M`!��U�v������e�&�u�E�B&v����d��!�Z�V����0��#�J����m�f����$�"�9���y�U�,�n_�:�.��|������-K���	�qHC�,5�y)���y2�C���� ���@Y��T< �C�!:�C<5����k'�p	=6���:EX����j~��V���o�2��<e����J�v�r�m�$[�I����C��e����hZ��$�I�Y��T��7���U"��W6�����8����4����'tG[�q9|���������K�3H��?!�6M���G���;�9��	���|}V����<t7��q��f�~�9��2i��{)���d�}�F�Qh��D}�������eh�h��w�=�%��b�|4�ZE#s�O��+<�e��X}+���)�k�'�f�!��$C���[n�8�{�.����~�)���y�l|����^Y�	�����^ ��UJ�X2p����������z����Z�H�B��������U��!G,�;h���q�W�P�S`������a�������de^����VKA���c�fHt�"��`�9�	�o������Z�n���JT��p�9b�P5�,��Q�5]g)Z������E�p��7F5|�.���*�z8�`����P�+�Oj�~��x��Wl�M���d
���Z�&?����� {��s���TVU�_��������v�8���:�i�:���x�x�u�E���5��bY%!3^3|�9��L���_s�fV�JD�Y�4o]����1Q6���q
]tC�v�52����u��L�:'z/�{�<
��.���l�~K��;(^�LjzF!�9(���
�-PMp�
����~�"��N�������}3{oQi�0����is#�$���>$��oo�>���_�oL����z7�k��t��,��� �8�S����q,s)R�{��v^�����r�C]�/��������_b��(x��\B'4c�D���x�d�,X��:`����Q�z�P 8^�f5��/Y��b�E#��K��f��i �f)��&nC�C�	��<���jj��)�����
�8��r'�����(���I�Q��VH�1��zAn����� �9
��_�)�T���V�����5��-nn����D���f]����jN ��0u�������km�{����8�H������������	P,�1HS/�Y#S��������t�BD�����b���hm&����K��	b����%�`��$c�����x�t�oX��:`�P��y�n���0����?���l���D�5�7gf�����M2)�I;�
�M����'��8A�v��������YU��b�Q��f����P;�Q6�O�C@;��W�=�R�������5v/���?��a3!�i�\&1�5���8$KVf
����v�o�1�0i��v��WN�b�&
*l�2{�m�r��
Y�2�H��8��1��W�^G�'>����n2��|j� ��z��P���.����~���5�p��v��3���J�E�Y�e� ReF�����[��&H*�J�N���2��?
��({���*,o�C���#%�v�Y
���K��[?DV���ps�.���X�x�haL�DL�R����k��@���RB+��^�@���l�W�����5bvZ��f�2���=k�V ������_����I�6�=�������X�\s1�J�&��;�8�	�d�&�
�y?������C?����CH`mB�ou���}l�)���}��_B�e�R���dK�������E�Ylf�����rA��D����n�_KE����*����EX���"�* `D������7��,�'gv:jE�f��|Zbwk}t�
HEo}(���;��qy�E}��#�l���i�ZOM-� N��3�Z�n��[�C�����F�8�,�����E,V��P	�.�8>�f^GrZ����
���v��.��S�����&�j
9���l���?�e�+FQ��-�?�Q> ��	��r�=�2�dWI^�����c�����r�=�l�����2=��	)�����	m������3d���T'��cOJ�"��F�Y�I�(m���_/�wK�R���Qb*��������B�Z�u	��3Y���X�R���
�C�������.�J�
syN��/�P�SQ���1�)�\��V

�%M8�r��(� ����u�����,I�4J��R�`�D���vn�K�����8��a��U�X�xp��2	�4�b�q2U���1(v�q�{�%:_���"���4��c�v��yMz�=�~0�j{.������>��n�	#�1���L}n�p�j�^��o#��������k���C*[��#������EbE'V��H�8��c�$���^va	����QAb�Y�x�c:)e�
l�xD�RE��B�A��<������%��z�K�n�q�\��n#�k
�:�<���Q].
V5��nbE��vQ�4�7F|�����;�r	>�3�q4C�u�w�v�7�7�I�L���fZ3+P��%;������N]�Ss�������K����D�gy
=�����p���%xg������������*k����<5�,Ju8H������Z�#�������E���`�n�y�h�1�B3-:��~r����SY���*\�wk�le�
9��B����������k@a.��5`�S(w�_c��X(m�I�T,,�;!�rGP�,,��Dh;�Y'�K�#�d^I�B���	F	N����q/X��{�{�=�z������y�6Q*���	��g�����@��WKb���u�:�|"?���Ot��xj�!d��D�$��K��)����]b�&j���m����Q��"��c�z(T�z��R�����#)�9�����M��z_QJ]�~�f�Y�������1�V����v{4�����}l��V�6��t�\�����=4�X&1hvvYuMx~�12��z��?��,�H�� �E�o������B�H*�i	y��
@e@I/�9+�E��h��%�A�Z��(\3����V/q��3&V�����Y�gx�J�2!�HM���$4�8�[9����!&=��>@4��z���3���^K���+W��:GV^"���9 J�Xa	���u�@��q���PsF�����Bo�&���W����R�5[�{�{�$��?
���"�d�@9�B�d�Y�O�|�I�`���f�%x��H�Q�F�
�j���wn*�,��JGQ������]����i��<�V�&o�;@���V7 ������|;��\f
�\�C���RX0��r��z����b�I��M�K����m�u!+RX}
�����*Om�\�
��Z��<h;����M�>�_���%D�^�c���y�*kv�_[@\c�' ���n-x��\U����	U�5�A���ms��-���%���U>d�V�k�����?�"��B�s��in�����M� 	k��k�^�������A	�wo���Q;�i�L��+���)kg4�4,�cJ���^{�%x7�8��f ���9B�c�eR��?!����7Ld�;���*�N�i<��q��u��H�|4o����"��R��F�;2���I�d�*f�As3�}r��;E�-�zX�������}�l�i^�l���PR�,�3�Y��&�3Bfo�Knn4&��`���
�c���T_l%bJ�0���i<L9W�*�f��Q�9���{�V�PS���3K�n�oX%D��*B��"$��{�2����5�\+����3����i(���/4��^.�K�n�
+V��+���u����f��2��^�de�"!��]��S6,�@?�@���j���%�pB7����k�Z��!4�%�,b^�a���K)
���,Y�'�! 28��������
�:��/�'�5q��j	>��h�g�U��L��3�~<3�W?��Z,t��O2�����F�m��?C��)O���4�>4���dU���9���������''"vS14��^��%x��6�U��{��qN�w���Q�/q�HB:,��C�C)b`L�k��w,�5x/a�� ��N<UK�Q1xk5�E�<����^����[
�������X��1�����!bh�G�[��A���V�����<�&FG3j&�s�������<�G�������fB:��8Q���[��J��e]gA��`�?�Iq|	���U��&���,�R�E���i�����k[f��UK������@K������X��"^�F�������~�U���tu��
���[�D[�	�l5r
�r�I�!�q�16Eh)��*dj�]�����`����k�n+[ V+������C;q�dv�eb%�4�^OD�lK>~V�?���_�����������+JM�����NR�&�{��gR�%��g
bZ>��[���#V���6�B>�������/?���n���~���������
N�~�U��=�3��H�;��ztz���y��tV����]�A��:�Xq����Ia��m-J����I���?kT%W�Z#U_�?;�j]�#��?��/������d��tr�e���$�����[���B1���+���d���*
7��EK�Y)��i����Y�
>l}y_"wO�����F�N��+VX��C*��W�\"�����<��.�8Z��J�-n=�p����
���l���`�\m��^�z��)d��|"�������SS�6�0tR�����	����z8	@�7�:!=��$-�S����U��64@�����eq</c�ss'��/��2�����E?;��`�6�B1�,����eY?9	�a��Ij�M�K�^��8�@����h)�	��-<b��!�x���M�����5�������������������*Us6l$|�H��O3c�����
�42[��z{���"�U��W/��G9\_A4^R�)��K�V$F,���N��E4h�B�[J�J��������9�����i�I�M�K��Z��N�4�1���^I����`�gX4�4`�#�����z�vkUN8Il0��W*mV�Q�%G��wP��cZ$�#�s��dZ�-4!��5:��~r�a���
�z��[����aQ�Z��cdh�a*w��c������hEj���c�M
~�<��@���-t7��8z|oW�6�w!����<��8|~CP�D$L|��7F|�MD�J��-�pv@����*���3��q�t���K��#3d�\H*h�F���9�c.��y$-k�u_s*���u���\�Z����o�};�5t?����"����b���s��5��Vl�)����#c�\r�����g������}��<#b�l����?0��I+v����Z G����B�����+*[ G��P��AAY�Go�@��i�IO� |���y���W�_%o��w%V��"��Ob�K�ZQ0����k����m�S6&�@1�@����vC���P`������G���Yf�-���J��V�����i�~7���"G�%U�bq
=Z���}��B��������:�k�U��,��2`���
x)���FJqo�&-��gb��k�*�?30x��]e+�b4�?��RJ����ihtE������'��i*�Ng��[�C��)�$���8�/2m~n�q��em�'��!��E/��F��Y|��
=ez���bc-����]�
}*e|Y��/����rW�x���H���!`��6�������
�${�������H��i�y���+T��#�6��u�)D���
��@z�O���jv��
��c�=J`�~h���@Yg�e�E�����v
���?���I���	J�\2�	E�����g��i ���?�����Cc�)����>��h���5t�&H7�B��F|U�L���f-:0l�w��?�DtM�y��SS/��U}��
\u�8��e-k��n>g�LIKG������I�� �TO��}c����+�5��;�������hR��������t�~i���W,�61#��C�w�^O	�����+��4���7�^[�k��	z��{�L^B���v�8��3�^�x�\��U�[T���z�raN2I��)�ZA��B(9����^4����	67S`��fH�f�}�4�qZ��T"PB�\���{u;�h^^X��
�&�d_�0 �B������>�}�#�(A���5t7�����MPE@#d��7>8�dm����R}��59�����-�h��i�����"rh�:'���c��d �
1�����K�#"98<����s��6�_l����%�XA%��M�]�B���J�NJ�9�$�u�`�\��cd�*Kiv��S��X��>��mp�-��v����X���i oqJ��[U)!�K��Y�O�|��ii�~�w�Z�n]o@z�d,��B7%������z�������o�N�Y��A�S
J�\zw���d���8)d�v����l�d�(�f�����
��?���g`>\�PZO��5�����i�����jhd�If'� ���\��Bh$4+��t�n����p�[S��>�	>X:�(���~�=�t��zI�%v�����"����L��0�T�������ErK��{pk
��u�2mf/R�x��tH+�B��
-���4u�����\�!Q��pu�����-K@�JDEj��F��
p�|2�� ������-����6��_�� ��	m���4�������#yz|�����X�@�}�*[,�A��l�2��!��HDz�y
�i�����,n���;���l�	�A%��1;�C[�$�0�
XUi�0D�o=�b�n|@�8��+o�{�d�����N��k���m�����LP��d��5vAQW	
Y��2�aS��Z\�>��������!%oLPK]��mu��&�u�E�:��r���d�G
�T�f(��J#��G�5�������8���YS����"n2��Ghc�A�6W&49��n��8{Jq�d�y�q��
aeaHV�Uf]aBJ6��@�8��VF,m"����a/y����HY�3r�{8���@m��hlG�9o��4�Lx�
@��D)k<����M��yj�!�;�b���-��5q�p�y0���h�0������`l�qX��2�e#V�bi��5[d��:�\q<_Q��
V��up���,R�x��P�j!��#���K���k��q��H}W�= ������8�ZU!U���9����L��j�FE+XB�"��O��D������������@g�P���Y�.�������2TotD44h�_|�������K��C�=�&��z��z-Lh^�q�L���l�X.��i��d�w�`���)�C$��&5���:-����	f���^C'
��,"����^[�o.,�H����x��VKmF���w�V������8�X)5@%�t���t5�?$/�@����96�O��<�4��K�={����)�cL+z����������Q�	'���!&��+3[��o	�D��H�`��[�d���7��;X��QH�w'���Ccn���B#�S~�����S�s��{4��4�ia,��"!���3�T�4	s�6�K���$�#���i��$��qy6������{l��;���.���5�9S�:�o�u�aa����7�YX�b�e�~���i�/��eqC�R�����9��:�R[4�������x��i�U}�nx�Dv>���UK�w]cwkvxA\�p�*X���w�b��!H��]�*�N\�fU4��M�H�`R��Rx��
?�?�E���V���G���O�YjA��-����]�S��@|�WC/�~�=�#�q(mb�Z5p�_�n�����������;^��������k���,Ah�D{������n�=V�`��+�K��,�&X=��������	p�'X��:`c��jfQV�@�����K����ch�n�����9�x4�?��^�5����9U���!(i����B�5v�N��1F��������%|���F�8���
_&4$0gJt�s�SS	���n�]c�BX�&�.p;���H�v8	a5o@X��2I�k��5��<�������lw���B�����:T��6��sE�LymP�MP����@
��n�*MW��3��UevJ��M���?'������k��	������%�pu����K���Vy��H1��U#x� �����a���:�4�{K���	��%�;`��:3�O�_�W�/��!�r����eq?9���@�P�N��[�C�5,��JE��:m�^�qy����lJ���`����C*(i��
�k�n*[�Ar��$6�R����? ��H��Z�\�n���3��X��(�e|J�Q�O�,{\[����M3A�<�e���n�)4?k�G���1��cxn� v��1�k�I�]��+Rh��r��5�M�
D�_�5vaP�	�E�Y�e��"Q�R�����:mx�����������;����i H�UZ���`�Z��OMv6������rB�m����5D�a�0+f����)2��������0F&�W���,��uc}�=�P�[�N�k�n��[������U������:k���j���"��N4�D���1���e
m�A���k����m5d��M*��i�`��B�qP������:����D�)Lw��?�����������*}-���,T������cfjB\��N�;�y�U��q�
8�����B��
������rS��YNP�T"]�}��X������Pf��p����<����,�'g�:ie��S����
b�\��
R�u�i�������0��
����>�R���E7/�+g�<��		@&�h�x�T�p<�����_"��]��)�g`
>L�PC�J|����hK-lY��:�Q��h�����Q��?�e�T��khV�c����~�����H���f�R������+����s]Zb��7����;�2�u�`Qf���L����P�X����x�}g��^'�|�����`h�4e�-��x�M0�Q}��Pr��S~QCP:�(����g�������� mf�"�F�Ly���q��y_T�*e�V���F�`�z���-i��T�d�|�G+���B�u���"�e�
������#+w5�� �
��5�%>\���\5�~�4_��?�6LWR�*��P��
�4��i��
�W��-+p�;�`:J/D��)��j���[��0��I���s����P2*}��{�L��i�{/�]�g���z,�P0�Q���Q��9����
��Ci7����H�
��r��1��������Bl�^�+LlOs���B��P�l����u�8��!T�p4����]C�X�# �E}�20�F��	�5�}=�T"CR��2��;A���B����o
	(�E����<�������"����}=�`�`a�5>��r�=��(�1Y�dt5���ro��������li��&OD��43#>��^���-����Hck�:/(,���!���Z��55p�=�(mU'��%x5P�������c�h>����LE#�2�B~C��<C[��i�$+^��9�]��4�?&@�R��C��[%&;�������[�^�\*����=4�1�;��i��Dvcs��K�#�$�\�l����Wi���cO�������-����k�n+��V�[aQ��f�.�b��e�\42��^����$�$�������K������VA�������	f1<��R+�o��F�d�	n�����N��S3!k�Ru��,������7�k��{4�������<�{�u�A�r����q-MmM��E�d������
��U2��NT3��=3�O���uU*�
G����u������T��{=d���_j���O�$R"w�k�{]!��������q��-r>#z�*����n����p���5x7�qi���35����FC���;�� ���c�)��Q�����wp���-�b�|8m`���]�+�eM�q��`g�h�8��H���z��u#��$yr�B�p�'�Mrh#c��~`	>V���
����W	^C�&
$��Z�gp���`���\�����HE���Y�LsmI_�?7���q+�8J��,���4�?4J;������lUn����>M�vr����
5����k�.�{uC�c��%���@�����$��5�1���f;�3S
)��R}�=�Z���k����1��1*��7r)j��g����c����	�����c����j�v�O��������qX�*��r�mXS�D���W�b����d� ���'!�!�h��n&��OM>D�5�^8�*�%��knW4�\b&��*�=�5v��������Dn��_6�H��xw�n���!��m8xSA����{k�g��i }H�;#c%k7p#�����Ol��V����'�,��u><�!*�����!r�}#�q1�7n,���f��]w���C��Wp�4����d��MP*��IOtwlC,�1�Z�;1�,%�y�TO�����'W�vh��z]bgt�3�v������gaY��L�v�5�,!����<%�CW�|�~��z���V�|�Z����c0/�Gk,������*\bg��N,�Lp]`CF,��D��v8ii���7
(k�\BnQ�`�_������!	Se�X-T�	��>9]�a'���:5����!�����o(W��6L��+�pV�d�#k�����H�dIO��<5��qJD���t	�c�7`��8K�`:�Oe�0o���� �[-�Ev1�TO��}cd��+���'�_���6���o=l!'��d�0�j(����?������)T��P����&����_��M�����ZOBpf��BI��R�K���V�\�8�=��9d��Y��{$�$���=�v�_��	�4����q���o���
(�|�M�M/k��'��Ak;��������z���Z��U��.U�y�!���J��}���7���|?B��2�Y�,��A�l��J��=�)�w'=����a����!B`�u}c���i.�q�%�������Z��cm�I��2��]2-�����R ;���o�(���������M����7L������,��S��%�x�����Y�g�e�<X�8�@����o��/�
tw�L�2�%�6����e~������y��"0���Y2��'�>G�<�����{�U�@�]E'�%x��H7��h;Q�^GI���%��#�@E*41t�H�!=��S���JoM�%x7��-������%B��D&a��c��EQRSV��������������,e�Nu�|8
a[`}1N!/�CW�J�Y��qX�yX�o�dkM�m��.K��u����!^D��D��!��H}�C\���!��n��K�����������3h^�!h�n��M�m��)�1�fNeR����,�fh^��{;�Y"Z������b��H^������";o�N����!V$c%9r{c����$�-(cll*���4#�I0�q�a��N-g`��,���f��D��&wV��� ��v�jl�V/�����O�A�C��*W_%�"D��r�~�{l����������������DDl����V\c'�a'�6��	�!{�WZ����?�q�ge����`�kf����z���{��������������?}�>����o��oY}������w�|���������_~���?�����������������1Eo���~���_��p�_~�����������~����{�3����.�����	����g���W�����/��0�����o�_���G��������{��_��������7����#<������#{�G��W���#{�G����W�������*?���)?�U~<�#{����<0zS/��^|�GV����P���M�X^��#<2~S/��^|�G��^�W����M�������)?�U~<�#{{X�*?�����C_��C<�7������xdo�}������������xdo���*?������^��C<�7������xdo�{���� �[��y��&o��y���O���b�A�[��K3� ��h��~���V6/���<����K9���t^��yho��z�A��B�%G}���V�
/A��<����K����&^��yho��,�A��B�%L}���V�
/i��<����K����:^��yho��@�A��B�%Q}���V�
/���<����K����N^B�yho��T�A��B�%V}���o����>�C{k.��>�C{+V��X�AZy��^��c<�����
��xh��q�*D���-D^b��xho����>�C{[�������"/��c<��bU|�U���-D^b��xho����>�C{[�������"/��c<��bU|�U������W!���X_b�yho��X�A��B�%V}���V��/���<����K����X_b�yho��X�A��B�%V}��Fo����>�C���U�<�C{+V��X�A��i�/���<���u_b��xho����>�C{[���������U�%V}����y�U���-D^b��xho����>�C{[���������U�%V}����y�U���-D^b��xho����>�C{[���������U�%V}����y�U���-D^b��xho����>�C{[���������U�%V}����y�U���-D^b��xh��X��������C{"����U�K�� ��}h�B�1Z}��^��C<��b���>�C{[���������U�K�z�C�o��������/?�����O�����������~��������/_~����������������_�~�?��'������n��_�����������o�|�~����Wn�?�����9���������_~�������o1���?�_���������'�,?��[B����?��������������~���������O����������?��[��������������k������O����/�_��_��/�_���/�o�~��������������?��;�����������_���d��������������������g������������,������?~��?��������?�;������/��Wi���~���?�������������w|���?��F~?��_���7��_���?��������������������m��8����\����������HC�3��O�"�8�x�`0���H����zS����O����_������������b���O�~�+�����_����?�}����C�_;����8������������������Q�}�����|��������
����������>~����������`~�?�����w������Oq�������������O>���}��_������_��'|���qS��o?�%�������������U����'��>o�o~����_��/����L����������n������_>����)��)��)������}7�W�������		�������)�B0���_���Q�����i��o�����4��e[��O���	�����8G��p�C��M����M�O�l-y��S^���������������G�>�V��S��n?��g������_�5�_9������z���Z����/���cWuy$���>��l�&
�;3��3�z������{��}�z_�O����Yg�vg^���%a�~��6q�|��g����)��l��pI&������K.xQ�7�.�&pwK4��^�����������}�Iu��&�=��v`{$���E���]��3�������*����i�%n�&�SB����
���7t�[�G���
���w�����
u���>�[���'�7�7��{p�{p���?{�O	e���rC��A��mxU�0Z����m����v�rCG��o	-�����JlMJl�}����l�l�%M&�����=�������a��C!:�
J
J
Jz$�l�>]�A(�t(���	�z�&<�0;�C�C����5�>��'~�-e��;y	�������6��f�Ylf��o(�v(�v(�v(�v(��
�*���c���������������+]�H��:��:��������@��s��c��j@�x��R�,3`n��%cJ�J���(�C�j�����%�9��������%���g�t4��|-�5(�����a}�������f��	��	�	\(�(�(�(�(�(�(�(�(��G���
�m�a�a�a�3��������0����	u�	��	5�	5�	��	��	��	�'�&����	�'��0�wB�qBaz���N(N(N(�M(��|�	��	��	E�	���9�� o*���.8�.8a����\n�"<��9a���	���2������J�J�J���v=�0euBv��j�j�j����P?�P?�0�vBmz�<^���x���`>����������LZ]P�^0iuA�{A��Q����
�V@/(�.��������e�E�����Y0D��������������������s)�(;.�
.�
.�
.���`��������������:pB�nA�nA�6#Y�T~A�qA�qA�qA�qA�q���Sb7L\��������
h�U��������6�aN���#�a�0ou�b�
��7�47�47,���o�������0�vC-vC-�������0)vC�wC�w�B�
�b7��7��7��7����������a��
��n8SkC���[��i����T�������a�������f�n��o���mU�
��
��
��
�b7T�T�l{`���m`��@u��������[(�(�(iB��@�����3WTK�.��!�=P�=�=��J����*�*�f�B�@e��>�*��k=P�:P�:P�9���5�G�hrB���\G���	T7��;P9�P��0?����s�.T|.,��P��B�������*>�#^��xa4���P`�0g�B�
�G.,��P��P9�P9�P��0��\��/^��\��v��t��t�,s�,sa2��5�&�A������.LN�0�����nC��B��B�B��\��B���������U�&[�d�o�lu,�
�%K�I6\o&o$[��d�o	��N�-7��,�)���09)�p{�O�r��-7��,,�p{���`��f�`��d����[���I6��pI���l�M���l��1�1�p�Y�Y�����`K44NX~\��6aU����7�H6<�LtL6\o�&l,,��[�
u�
M�
/�
�;�Q�h����ek%�]T���k�k��T%���PQ�P��P��PK�,,��XB��B��B���vf������%���T�f�l�-����F���V�@T���l�)��QY*X����2�4��QY��dC���T%��7���`��A��A�AU�A	����oA�@4�������f�4����L�`�I�D��,
�W4���K�&\�7��;�l"b����.Z��Nc�)�v>Cc-y�
#�
�:��x��a�:��l��;T�:�b;��u����������-��FZl��r��8�a�]�r��q��u(N�t��t�[���F��R�����!N���@Es@�j@����T�
�:yt`"���8&���
��
��2�z7`)��v�`���
��+;�PP��f��
?%����@��@��@�0b�<�	� &��'�y'�&�y',i�����Z��L(;N�'MX�0�G2�G2a�����%
S�'��aB/m����:�&�y�]N�]N��a����a�	�q&���0�5aL�7�HMX.1��
[��3��L�`�����������������b,�i�`��hf.X��`~��q���/(^PN�����/�^P���������������	���|t`��%�7��/9_0r��n���������!� ��lx���u��&X�Ifo,����L��L��L�����K�a6���6�l�O���a�xC�aC�a���
C��$�>����V�mh�mh�lX~�a mC�����Fv>?�<�<�b�������F��y}��hT�{w�Qu`b��z�������s���:�<P;P;���@��@C�@C���*H��������O��wJl�`�P^h�A��B�����G\�P��0&z�oyY����<L~H����Vn|�����P���IN9�p|u����l�O������`�%a�Fw�l;�	�KenN��D;?����d�o	o���ku8/.��\��k����l��v8c��Y]��h�n��v����nA7����=N��p"K�Y:����d�`���	������w8�"��[��K/^�n�D�s��>eg�qwm��Neg�t�m����^�
O��lw*�e\C�d��5T���z����-�p�Yt������d���/�k��a��`�g~���`��fc;���a��d�c	�(�O�[+���@�:����0�=�R5a�eB���6�r�a_���v�6��\B���Y���j�}V;���]/�D����
�
?%4a����l�)]�g���\0gA��uC���g��9�
��
�6t�+
644|-]C�d����v�P���z��7\(�-V��l��p�������
��6���0�����[�m����
#�0]kCAs����dl��'���M6�&�:=%�m�E��.R����5���1x�'�ZTu��)�0���1no���'��n�/k��a��d��sa����lx�/b]��Qf��e��eA$[����X�?`�|��>�h�$,Q!�p������d�O�"��k�[�J���T&h�����v��[C�Y�+�L�
6�u���.l��{���`�%�����>���
X�=Jg�1���|g�I��lx�tVl�@l�,�lw�t�J���p3�7������e��VX'����Z�Xal�!'��R���%+���3X�(z���5q���k�f��%�>i��
8W>�,�6�f�
���2!����K���.�c��#�f�v��Z>FE/z/3OfyXZ��
����e��lu��,�	c
�v��1�8�,�~��]3�o�v���D����Y6����:����M��=��q{���c�tf{�
06����W,ll�5d��;�,f�,XJlf���vV9,�-�L�������L����l&O�=i�<�I�.�w��n��R)��
H%[]�N�	6�p�!����f���yl�t�����
6.�)���������pv��l���a[�
[�o��;�pMX/�d�����6�6�(l&Vm�)v�N��u�M�{����.	H���'��u�d�`����l��m�_�����6��6��l�_x�^���a����U%[}�`�%a�N���6��
6l&�%��we�L�Yvi��rW���le����v����%[�h�f�L���qJc:��%��v{���'�]���4���
744�;6���l��]���3�YNK��U�Y�!�����G����c�*���&��>��vl6����`�	�)Wlh�V�r�(� �\���VX'�	k.z�8���f���<p�������������y71�P8YP�uS��
=������l�^�T��1��p`�b����h������"r]YW����`�G�&*��U������m����U|`�^��de��K�J<���r�
O��/�/��&�%[��`�n;�npMX	���q�VG>�pIXx$���p����.�i�l��\8w(��R9�n�T����*���/LZM6��,H�l�&�6%����0���f�f��d;k�1�-�n�k��l������ec������vOCcCm/Ln6�.l�l�64h~7�	6SO�
��i�:�,�{aRv�����;��������=)�����L�v�Ig�3/���l�-Y*e�����|��|g��d�;V������d�k����2wU
(�$q�/�vg���\8�2�p���1��9X�f��6�����lgA�l$��@/�z���d�+�s�4'6Lh�L��3a c�`��O��l���1a���5�lxt`�5�M6\oV~aM��5�Nc��^9���@�gB�gBoBef�x�[�5��*.��6�+�6�Ul�%Y��`��-w	��]xM�����e�u+��p`n��z���
=y�B ��[���vg~� �����{�o	#
*�����n�m�YK�d�}�����k=��=P�>P@v���
�������:�w�w��?��`����� \�e0�Y!�0��j|��%���_�2\Xktav��)DndW�����I�']��t���
�Z'Pc�P�������V^���~U�v��kW�lx���gG�>�aI�/\}�.��I3/\�����e�����������p�+%\.93g_�\r&�%�%�'��o	g�����N��{2S+���������
���k�������p�I+�2
���_.7�j_�<�LP}�rYX)H����Z@�p�=Y����S^�,����#$}���2'\�p��T&�p��x�p�}���_?g��Yc�W^�\q��5�
5�E9�����V/\�D��������_�����X����X����"���4���g)&/\����o�����_.w�T�]G�.�g�������e�B�Q�.��]�����p�,������,�7�2���8�pyBe"A�r�k@�p�@�V�/\�-R����@��Q�:�p��2���]N�L�2��Z/�p�[\���KU��v~�r���7X+����.����x�r+J�O*�CZ�C�gC���u�J������p7w��.���q�4C�4C���Z��px<������OY�$E�)=�)Ms��=�2�?e�rJ��u�O��Yd2����)s�];��K�y�T\�Q���*�tX�T��L����)�e������)\K�/�%�e�������d�|����J����bM�.��K6%XR
]�]l�u��_��1'e�%��k�����d��^���29D&�.)��a/�.���W���q����|�R�vc!^�\s���>[&�lY�!�O7C����(�+����2<��q�l�Y�����"��-��-�+�4���.�4p�,}�2?yK���#��#
�#
�#��G6�:�����������.���B��Be��������#��#E�#E�#-�#��#��#ME��4?��H3�H3�H3�7������H����+��n.����Wfn�[�J��
0{���J���{�r�eZ��)n���"m����%��+��+h7���KCQF��t+�t0W�T��b������,���_����;k��)X	��Y[U��J8{������eqzb����Uq�V���w�P���*�<�lymAs(�r����Z�kx�p���e$^����r�[�]�f������p��fqRh��f��su)U6K�<������.?���H���r'�:����"-����+�lV�d���#$�g��&\�,.w��&�
8},��5i>�QX	�OEs		����
����4q�k0�p���#Q�����I3%\�7����L�tWR�p��pHP��K����%\.�|����px�{��SZ�'����|�eD��.#�]
9p,S��d�\������C�@�eHckH�e���p�>p������A8U��	���������/^�e��p�������Dp,K�CB�g8���!!��ghJ����rF�e@aJMa���)
S&��YU���r�F��������.�\fWO��Oi��Ug�L��#
���&�-K�K��p�C���H�N~�|�dM�p�=eLv��|B�tq�tC�L��2>��u���[W'\.�t�`c���+w�P(�]e�*;@'�9����1We����e���u�K8<B�su�eN��N������D��s��������*`C���_./s�5e�l�p���'s��|��|'�TBe�l�px��v�	��S&_��^���������B��Z�uYJM�i��p��7�,���$	gQ�]d���o�\���\v��R]R[������wl��d{�&;\��t�;c.�r��9�f<���V7��$���4��
8��`����KKZ��I��+.
����&{$��E�w���.�������to]5h��������V=����t@�<CC�!X;p)�
����y�R�&+p.���@��%����$�-�����;
�\ry��,�;�\�l�]Bx��S�\����k����U7Y&p�^���Z(,�I8|����=oy����V����d~�]w�����$[���H���&M&��re����lr.x���26�l�.�'�-y�]'��f�2���q�w�V��J8�����p��N��r:h/��Cw�}w�\�����,�����rCzi�b+�.�p��'\��<��Y�w�\���.>p�)$\���w���r�ph����	��~O���e����� �l[�ag�.�?w��9�����N�n�_���5]�����	��������h�0K���iw�����:dt������=\*�0��e����gn����K!w���.����\�rg�C�cu�+��)B�����y�\��Gc��5w]����^���l�i���g���!!���C.��E��'�e�v����'�]�	���:!g��8	��I�w���l�Y�:Jsq����V������lap!
�K��S�.�Pw���K��ni�i�
����.-t�:�!��&>����9Hv��y���5�,�!���\;����y9i+���Z.�`�{	�����C&���-�98b/��|�.�?�.2�������'���<�r��M��o`rr�(Wp�K��|�DC�o�2�.��+O(������q�s����s��np�j�����;�e�)S����N���7��{��V�]j��a�����9�u��S���<e/�)�{.�wew����
�X���w�pNE�]�i����php
��?a�5����{��g��jq)-����F�M��|�������r�b�]���m��-��%��%���*6�,����w���c��h
8\��R����&\�r�Z9e��,�Ep�"8S��NY
pyo]��;��&��R7������`)h��Z�As������N9{��,
�s�:z&\~PW$p��pWC�dk�]�l������(����}^�>���K����O8��p�����	��e����FB�!�V.YW��D��;m.�����'r��d�U��4����%gw�Kv^e�v�	��|u�����),Y�px@�<C��k��6?��8�L�]V[��:n�#m�#���j���$^���l&�XpW�px)��V��r��]g����$�m���eq�?��-W�i�[�����	����%�9[�Ty��+f
��Y�l��Ks���%�6�L6|����plj�����^p��0�N��K��2�p�����bw
r���[Vm�2\(d������8\z�.��a����(���R 7��%[nC'?�����������X�\a����w�)�RZ.U��7��x^RZ��7����������:�\�������Mq��$�\3K8��G�r�7�rY\uR���kMw���������^W)p��r���N���#��G'�M����GV>�e��
��rO�.��y�w2k�]K�G2��V���5��e9r�����w>�)�E�.�"�L<eH�h� K�]/�#��.�����S�<D0$p��Y�p�,tU�W���_.K!�N�;R�?R�>e�<���T�#���zoX�wd'�#�����i�����,���:.U���l�g
��\�\6�.�*�.��p�A�\���z����Tg��[�4w����.��Y�$���3']�n%���27o�N��0�8����Iw�����OwRh���<AG>���Ow����1���V'��xeU	g��Y�l�Y��Y�%�,�:�����9X��WWb��^q�6��V�p�[��-��J��p's����p�]Q���s������m�Y
��;�����L�x�p���y�[�p�&���>t���Ko���yY����@�Kp�����ZX�Y�YA��^i��^���p>H���[Kg�|��7p�T6��6O8�-�e����N��@k9,G!��,���n�����;����e�$�Y-g���NShp�\�Y=u�]fH�Y�v�Y;�����X���x_�����n�s7�
A_6��#4�;4���`1X�Y�f�'��	�b�B.��%O�f�Z	w�s���~\��I��I54�r�\�A/��i�RI��($�}����_��*^8]�������ge�	w�����n�Y=X��r{�
.tB���^F�������.��!�����k�p���Y�	�w�d�	���R�p����\�5�N�I8<��9�w�aw�'���W+pV����u�_���.p�x/��
=.�8���!�/����e:�#�p)��q�	w���Z�e��b-	g�|���	wt��nR=2�h@%w���/����Wn���[�;����!��!�l��������p���N�E��6�����2�f1��t:};�.+g���	w��(�9�wA�KG��7��H8;�3L"�����g�Jw�E�](t���	�K>]w�fj/\�rh�p�p��|��;Ma���v���W���3g�e���	w�rw�g�������V��r������]�����W���m�]"q�]<a��F�.��a�LNLwl7�$��w��)-g&�e9.o�&9w�3���_~JuRh�e[1����"�l�}.�U�p����|�������'��;4�n����*;z�Vs�������[�e�l��e�y�q��G�wXo�3<���1��<Li	8���)�2����}�/?;B�l�
�oX������3U���A�8�V.j�,eUK>�2Wt��0�"��9�#c��62�?&�Y�n�d��9������1�e�c�����c���K9M���n>C���pW�6{�,�,���9��pW��p��p7�.���g\�,�e�����
�����|1c.���r���������p�����&�S=o��,��U�|�����m��%�.{#��U&|37��"
�s+���U��SO��PTX�<LTX��y�������&%�	���}��3�"���g?B�*n�������&3p�'�!w����~����+�J�fe����
�t�|V����p~���VyX��g�s��,�3�n8p�]'���l�wy����|�]�;P�,�'����2X��gw������{9'.���<r#��u��b�����RfK~�d-	g��	���S6��H8�S$��
]R�O�S/�tpW}�Q�/w:��3W��p\�/K��Oi�{|�n�w��~Y!��P�	83,���s|7\���|������m�Y����d��&>oY��pV?���������^�m�l�V�3�8��
��M�
�sY��)���������]��'�e�m�T:��a�3�����l'8F��3Cq��r7.?���=�vFh���pfVd�]���Yr�YM�����	w��,�u O���&�Yw6��t������	wjH�Y��^�qnE��[�	3��ZpLm�����d�!	g����}�X�"�N���dv���r��lg���s��?�������:c�y����]��`7V��eG��O���u�
��)�pg�m�d�������.Z!3��sA������}����Pv��l�O�,[.�,O>�m�N��
xw��)�����%�E�OYN7O8����8����
��gM��i����>?.��������Y�J����[�K%8�������j��:�'�=�	��|:q+����7���T���:m;���3��<0��pv��J��:c�%�&�=s	�?�e��p���*�p�/,���J4s+�:X|1�/�,����^9�MS����pVR����N�l'��0�/��N���p&A�5v�V�����>�����	g�s���q+�������e1�,�,�7��e	8��p7H6���t�������,������4����r�C�����2�^���~�'�'3c��1������lVY�pw'���~����`?���]�����v�KN��+C~�!�b��	g!�����5QL�3�e�p�^p�)�p�U�rY�p�Y���Uq���z����������������yw�����2������^���e�,�,A1���z�7�+�9�_
.�f�	�?�E�:���p�r/����$��m%\�r��)���)��N2b����{L$��R9��Z���p�p�}�9�b�B��.�����5�.R1`��.�\�����>�����{�VN�
8�G�p��4���Q��	u��7�����v�$4��Kf�����nV��p�C<�����"��\'��Z�EY�K>���]dk���	G��������F�N��2�eJ�e�������|8�b�E����p�op�M�e&�������]@q�f�	��� f��p����������^������8k����/w1���^�	w����gC+����;�?���
�K�]�������Y����J�k>�����/w�~w*T�!�[��LO�s�L�
�S-������\�}��)�T����\�z+����,���~y�7[�������Q����l������4p��nY��K���w��a�f�]���������5��2�"����	g6hNf��|w���w�#?(�xp����	g�rd[����v�)�������f{%��=��L����ce$|3�9�.��H�5�.�p :e��*w�9�X�{��xd�oN��k�����pN����
PH�S���R>�G��e~9��rX�p������V��n��[$�Y�9>	��^�+�}s|����8�����z�+����C�����+w�������9�"7���]9�#�.�<��&��I^	g�\�Y�B�������W�@���L���K1�,\p��8���[	g�E��u���/gZN�Y�E�'���p��'3_8�����w�brK��nq#���5�p�>���������	�&�e�s�Y����ajK���9��]Y��g�%�,A$����a��Z����;'��������
{�|8G������Y'	w�E��t��'��o�pxB�(������;9�������}~Y2q��C���c7n+��

}�
�Y$��!rP`�YN��C��������V�Y�����8�%���j�@�����ba��;����/>���=J�<C����������
����:	�A;QN�K��B.9�
}����G���l��'�����z�nv��Cl�(��q8)������;���
/�O3O8�o7��ysT \����$�y	g�s�Y
a���t��Kj�!�>����(�����@�s���r����]�w!���k+��KL8��.�?���2bH�<�B��^�p��*���iM�s�.��+�@�`/�=���8�+�IDr�_�����IF�a2��)-w
T�����^��\�
[��gxB��L8#����	���pY`���I!3�[�AaY����3P�,��fg-=.��t!��-���KI8�[`�ON7���yZS�<��D�vM����LP
��������9I8<��XW�5�9��m��m����;c.'�4�l�/�[2�p���J��]��%ss&\��|�%�{����p�����9~�.��yr���9�p'�Y�|��K
]��{��kp�[��o�r�I_�jqbh������	w��k�Z(�H���$��]�G��]p��rf��pW��|f�����^8{���1��O�e����$���nX��l	w%8wi�L��m�yU_�!ZNh���p�l����M8���E�r�(\s�I8���%\Zs�E��V��[�L��������d��#�����$�#�����9����3����y>���4�p�������d��8].������)��,G6��a���'�\�9l�p�y�u�'�<.��IXWN����}~��ze��&��y����]uR�d��h8�����+S�"-������+��������;������[��������N����n�>�#���g�Z��������Y_:;F/������Ozew�K?������X�����:���}��f���J�`�h�]��o�{�,w��3���35����0E�>5�U�g*�o������Y����� '�A��I:��?��V���u�m�^���
TN�b�/����*�����|�����Wa��Kgi/��b&������P�t�g.K�y�,�'��0f(�A��{��s{�������
'}@#���RJ_:��|���he����
�`���K�aY����h��:�[�^6�3]���b�/
'I��%�<w;v�����u�P;���G���z��
�0�������������I�P���Z������tz�@�������p()
�����>`���7�G�,JgIm��rC����|$}��
/�����L��H����������=�������M�3����(\.�`����X�����AI����g��������r�_�v,�M��C�Z�@N����y�0�,��,��`�)�P~���te`�u����h��*�������D�f�,�,*Y��ra
m�a�{�ugu�\��%�Kg�>���tX����j��%�GN����.j���U}������'lw��[���@���v�?�Bw����`%�MM��._���t�<�.
������Cq��M�{���������g��fi6�1Aw������t��<���H�s���fa$��L�{�����������{�.k&��R
����t7Y����T�p�{�������W�tg��M�z�.�U�V�=�8��6)s���tW"�t���tW�S�t��{-<U�U�I$�LAw�JA?.�0��e
8���6d��L=.1g���&�&�������t����LAw�l���E��#n�Gy�E0��tWd��G����C�l���G+'�
�M�q1��_��3�T�2A�B�,��������whl�.��n�t��]�vy���a�f��)s�^�{�&��W�
CI���d�j��7M�����.wL���Xp�s���l(��U������_��mi�mY�t�a2}��-��^�����z}A�}���I�����L�.9�����s��Y
z����#�n}@3���f/�
~��� ���N}�[��
�l���u�qC9��^���7��e�D���:����z���}����iIwn��]=f���G3��#��Z�����E;L�	:��~�V�tWu�rt�����[f.3k&���l[��Iwz^�]}D���~�q��������d���wo:,�n9����A������^\^^��Ew�t��)�Nqzs&j���H��)l����3�G�r�2\t�	�t��z��]�;l�t�������������c�,��)?-�h���L�<.�$�.����[f����}�	DIw�x�']�M��i�Iw9bI��F��o_��A���d���CxQ�,���f�[�g�+oN����b��Ep�I�����AAoPo��C�i�YA_.*�2�u��#�]�>]�O�ep2�������Tr����vIw�{��d�L��O�^]�,�pz��Y
:��tw$]nw'|����'�'}��X��d�j����r�A�.��t���Y���
������������M&���(
:,\O�t�P���zunM�]�B��S�{�A���
?��Z���${��(�	A.��t����8^��	�,�M�K\��n|�Vr�t�����Y���(\��M0���txV���u��*���hg�,�hgO+�Q'4�fY�\�	���i�2�t�[��ew�/��7���L��]W���[�
��s�]:4"���s<v�5��;c#����eCr���#�NcH��}����<��=�.����	/��o�n���{��@��}�~����-A�N^�E�^�!��	w����~��	�������- (���$�����&�#[@P�3���^��wxNs�����"��e�|��[��10r������r��N�K�M&F6:zsQ��w'=$�E��>\��h�W��s���\����j�]0bt��tJ:��z<�r�a�)����A��~z��89,����M�GN�roS��-��l��g�.��tJ	:���2����)�/�%�}�^]�����#k�!\~���,Z��>\D5��6t27����#J���I:t�W�{��/�P�t����j��.�}��*�F�
p��e��K���jj�r��~]rX������G���0�R�t�<2=��]w�48����NW��L:����j�.������Iw�L�P%���&���<����}�`S���W��9s��]������+[e}�u0W��
A�n���A���~]������3�p�����WM�S��>\S��Ow�GV���I�3�����_���t�Uky\����G{��6w��\JN��G�������6'�y�Iw�Aw�X�]�l�q�u�a������A�F�9��&�E;�O��M�^�^�������������t��t8$���~��50�Lr����5^�[�Q&t:����Q������Q�/���vs�����w���b��d���C�7���XrVX�a����P:�S��Wt�tl�l��]�F��|��O�Y"��)g��N�.�0������K�
��n������n�[�K����������W�#�v�2Iw��Kg{&����A���W6���]�<�g��C�����AwN|��n�e����D�t��6��DM:|;j�%��[iN��pQ��������/h���$�)n+��P:���7�����~]�D�]���e�=t'p�tg������_�O���F��$3��m��}��E���V��pWx����%��t�D����7�v��Iw)�I�o�,��3�U�/������������������������n����oy�����t�h1��3;�j�G��W���:N%����.q1��Id��~�-�Iv��O|��$A����f�N3{�Q5�M��������tw��P)	w��7�����5�?O�.}�d�*{9N��l�����*�e��l���G�	����I��J,��e}�+���A�G�����m�Kzs&���:��n)D&����~]���$]I��J���]t/�.���4���q��	3��JIg�u��Q���v���Iw���/H��vO�k�t7�(������1�#���Ip�G9��~���l�,��-&�u�J�a���Y�a�v�k���Y_�KJoOV�=�a��Kg�[�]�{{�9p����rr^:�%}�P|{nyX�0���$�1�)��M���p���M��32��	KI?L�i5�U�y���������j�YVd��;M��Y�/�e�%�]b_L�M�v_�]���_x�Zx�0�`n�������3
��x��K:��t������$��TL�:�����J��:�����F,'����t�W���*u����$}�b�V��jv�rwX��rt��W��Z�d,��t7����"���47��i�t��������+{���SK:�N�+nm��ZE�7$�����G��rX��s�;�#1���L[�2s�����3"���L�H���S�3�V�ug�=��8�)�oH�S:�Y�����N�q;��>����9�A?�L���\����e���7e��:���1n���w�c������C�2}V������R�^:�'��;z��gg�gy&?$}�7��LQP�L�L��������������1G�g������F�d�^}_��������rX�~N�,�2�����P'K��Y����I�������0>j-���G�J^�U��`+��>+�m���?0�p����G_��|�1������"���5�Vf�Ge��Kg=��~�X�����G-kh�g�
���e3��<�4�9�2���>�^�.����YT���tY��0�������e�$���
Wu��'w/X�sO���e���.�k���V�����]��g�/�Z5��W�5��5�k�:�-LJ�#�Z8����y���n��w��k���f#7�?-|'�Y����z��7������n&����vs{��=�r��k#}�(���y��B=�yfo�;���Ck���&��������F,��9au���������3#�e��_�%�+s�~{��t����d��5jY��.]s���{i�VX{���=L�����<}�
;-�3�%�,J���efB�T
I|�#!/�e����|Z�,��g�8�Y�}��_g�~���Yx���KjX9��Y9}�
������������'�����(�f�e��47����Z��m��|��dW'��!��}���>=l)f	�6S�f��{��m]��^=�\���o��%{��g�}<q�1Y'l�'�E��{�p\U���l�y�l�r����^��E���G��;,���w�+������_��G�~����?}�~�����?��������?��������w�+����O����w������~���_���?������~�W��O?���?����}������_~���w?|��?�}����G�2}�?�����������O���������~�������35������/��/������_����k����_�g��O���o�����?|��/���������������O���O>����������PKqP�S��2$�PK���P�l9�..mimetypePK���P����[[TThumbnails/thumbnail.pngPK���P���gl,�settings.xmlPK���P��h���$manifest.rdfPK���P�%Configurations2/floater/PK���P�%Configurations2/menubar/PK���P1&Configurations2/accelerator/PK���Pk&Configurations2/toolpanel/PK���P�&Configurations2/images/Bitmaps/PK���P�&Configurations2/popupmenu/PK���P'Configurations2/statusbar/PK���PP'Configurations2/toolbar/PK���P�'Configurations2/progressbar/PK���P�O���,�'meta.xmlPK���Pq���(
�)styles.xmlPK���P�u��1,�0META-INF/manifest.xmlPK���PqP�S��2$�2content.xmlPKe9'
test-saop.shapplication/x-shDownload
0001-binary-search.patchtext/plain; charset=us-asciiDownload
From ba3d550602bf78f35743986dbf501ebc3075e925 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Thu, 23 Apr 2020 13:49:37 +0200
Subject: [PATCH 1/4] binary search

---
 src/backend/executor/execExpr.c           |  79 +++++++--
 src/backend/executor/execExprInterp.c     | 193 ++++++++++++++++++++++
 src/include/executor/execExpr.h           |  14 ++
 src/test/regress/expected/expressions.out |  39 +++++
 src/test/regress/sql/expressions.sql      |  11 ++
 5 files changed, 326 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c6a77bd66f..c202cc7e89 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -49,6 +49,7 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
+#define MIN_ARRAY_SIZE_FOR_BINARY_SEARCH 9
 
 typedef struct LastAttnumInfo
 {
@@ -947,11 +948,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				bool		useBinarySearch = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -964,12 +967,58 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				func = opexpr->opfuncid;
+
+				/*
+				 * If we have a constant array and want OR semantics, then we
+				 * can use a binary search to implement the op instead of
+				 * looping through the entire array for each execution.
+				 */
+				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
+					!((Const *) arrayarg)->constisnull)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					Oid			orderingOp;
+					Oid			orderingFunc;
+					Oid			opfamily;
+					Oid			opcintype;
+					int16		strategy;
+					int			nitems;
+
+					/*
+					 * Only do the optimization if we have a large enough
+					 * array to make it worth it.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
+					{
+						/*
+						 * Find the ordering op that matches the originally
+						 * planned equality op.
+						 */
+						orderingOp = get_ordering_op_for_equality_op(opexpr->opno, NULL);
+						get_ordering_op_properties(orderingOp, &opfamily, &opcintype, &strategy);
+						orderingFunc = get_opfamily_proc(opfamily, opcintype, opcintype, BTORDER_PROC);
+
+						/*
+						 * But we may not have one, so fall back to the
+						 * default implementation if necessary.
+						 */
+						if (OidIsValid(orderingFunc))
+						{
+							useBinarySearch = true;
+							func = orderingFunc;
+						}
+					}
+				}
+
+				InvokeFunctionExecuteHook(func);
+				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -981,18 +1030,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * be overwritten by EEOP_SCALARARRAYOP[_BINSEARCH], and will
+				 * not be passed to any other expression.
 				 */
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				if (useBinarySearch)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_BINSEARCH;
+					scratch.d.scalararraybinsearchop.finfo = finfo;
+					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
+					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
+				}
+				else
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				}
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 113ed1547c..5bebafbf0c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,6 +76,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -177,6 +178,8 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
+static int	compare_array_elements(const void *a, const void *b, void *arg);
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -425,6 +428,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_SCALARARRAYOP_BINSEARCH,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1464,6 +1468,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_BINSEARCH)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpBinSearch(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3581,6 +3593,187 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it binary searches through the array for a
+ * match after sorting the array and removing null and duplicate entries.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR.  Of course we short-circuit as soon as the result
+ * is known.
+ */
+void
+ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		result;
+	bool		resultnull;
+	bool	   *elem_nulls;
+	int			l = 0,
+				r,
+				res;
+
+	/* We don't setup a binary search op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (op->d.scalararraybinsearchop.elem_values == NULL)
+	{
+		/* Cache the original lhs so we can scribble on it. */
+		Datum		scalar = fcinfo->args[0].value;
+		bool		scalar_isnull = fcinfo->args[0].isnull;
+		int			num_nonnulls = 0;
+		MemoryContext old_cxt;
+		MemoryContext array_cxt;
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		array_cxt = AllocSetContextCreate(
+										  econtext->ecxt_per_query_memory,
+										  "scalararraybinsearchop context",
+										  ALLOCSET_SMALL_SIZES);
+		old_cxt = MemoryContextSwitchTo(array_cxt);
+
+		deconstruct_array(arr,
+						  ARR_ELEMTYPE(arr),
+						  typlen, typbyval, typalign,
+						  &op->d.scalararraybinsearchop.elem_values, &elem_nulls, &op->d.scalararraybinsearchop.num_elems);
+
+		/* Remove null entries from the array. */
+		for (int j = 0; j < op->d.scalararraybinsearchop.num_elems; j++)
+		{
+			if (!elem_nulls[j])
+				op->d.scalararraybinsearchop.elem_values[num_nonnulls++] = op->d.scalararraybinsearchop.elem_values[j];
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararraybinsearchop.has_nulls = num_nonnulls < op->d.scalararraybinsearchop.num_elems;
+		op->d.scalararraybinsearchop.num_elems = num_nonnulls;
+
+		/*
+		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
+		 * rhs values are guaranteed to be non-null.
+		 */
+		fcinfo->args[0].isnull = false;
+		fcinfo->args[1].isnull = false;
+
+		/* Sort the array and remove duplicate elements. */
+		qsort_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+				  compare_array_elements, op);
+		op->d.scalararraybinsearchop.num_elems = qunique_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+															 compare_array_elements, op);
+
+		/* Restore the lhs value after we scribbed on it for sorting. */
+		fcinfo->args[0].value = scalar;
+		fcinfo->args[0].isnull = scalar_isnull;
+
+		MemoryContextSwitchTo(old_cxt);
+	}
+
+	/*
+	 * We only setup a binary search op if we have > 8 elements, so we don't
+	 * need to worry about adding an optimization for the empty array case.
+	 */
+	Assert(!(op->d.scalararraybinsearchop.num_elems <= 0 && !op->d.scalararraybinsearchop.has_nulls));
+
+	/* Assume no match will be found until proven otherwise. */
+	result = BoolGetDatum(false);
+	resultnull = false;
+
+	/* Binary search through the array. */
+	r = op->d.scalararraybinsearchop.num_elems - 1;
+	while (l <= r)
+	{
+		int			i = (l + r) / 2;
+
+		fcinfo->args[1].value = op->d.scalararraybinsearchop.elem_values[i];
+
+		/* Call comparison function */
+		fcinfo->isnull = false;
+		res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+
+		if (res == 0)
+		{
+			result = BoolGetDatum(true);
+			resultnull = false;
+			break;
+		}
+		else if (res > 0)
+			l = i + 1;
+		else
+			r = i - 1;
+	}
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararraybinsearchop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/* Had nulls, so strict function implies null. */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* Execute function will null rhs just once. */
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+			result = BoolGetDatum(res == 0);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+compare_array_elements(const void *a, const void *b, void *arg)
+{
+	ExprEvalStep *op = (ExprEvalStep *) arg;
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) a);
+	fcinfo->args[1].value = *((const Datum *) b);
+
+	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index dbe8649a57..ac4478d060 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -213,6 +213,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_SCALARARRAYOP_BINSEARCH,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -550,6 +551,18 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_SCALARARRAYOP_BINSEARCH */
+		struct
+		{
+			int			num_elems;
+			bool		has_nulls;
+			Datum	   *elem_values;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+		}			scalararraybinsearchop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -728,6 +741,7 @@ extern void ExecEvalSubscriptingRefAssign(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 4f4deaec22..55b57b9c59 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,42 @@ select count(*) from date_tbl
     12
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..3cb850d838 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,14 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
-- 
2.21.1

0002-add-GUC-to-enable-binary-search.patchtext/plain; charset=us-asciiDownload
From 655eaf54d496aa471ef9ad5ff8d28d2c50dd0c38 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Fri, 24 Apr 2020 15:46:00 +0200
Subject: [PATCH 2/4] add GUC to enable binary search

---
 src/backend/executor/execExpr.c       |  7 +++----
 src/backend/executor/execExprInterp.c |  3 +++
 src/backend/utils/misc/guc.c          | 23 +++++++++++++++++++++++
 src/include/executor/execExpr.h       |  3 +++
 4 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c202cc7e89..29a5b06852 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -49,8 +49,6 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
-#define MIN_ARRAY_SIZE_FOR_BINARY_SEARCH 9
-
 typedef struct LastAttnumInfo
 {
 	AttrNumber	last_inner;
@@ -979,7 +977,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				 * looping through the entire array for each execution.
 				 */
 				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
-					!((Const *) arrayarg)->constisnull)
+					!((Const *) arrayarg)->constisnull &&
+					enable_saop_binsearch)
 				{
 					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
 					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
@@ -995,7 +994,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					 * array to make it worth it.
 					 */
 					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
-					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
+					if (nitems >= enable_saop_threshold)
 					{
 						/*
 						 * Find the ordering op that matches the originally
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5bebafbf0c..f72ff52f02 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -87,6 +87,9 @@
 #define EEO_USE_COMPUTED_GOTO
 #endif							/* HAVE_COMPUTED_GOTO */
 
+bool enable_saop_binsearch = true;
+int enable_saop_threshold = 8;
+
 /*
  * Macros for opcode dispatch.
  *
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02fce2..ff9143a4ab 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "commands/vacuum.h"
 #include "commands/variable.h"
 #include "common/string.h"
+#include "executor/execExpr.h"
 #include "funcapi.h"
 #include "jit/jit.h"
 #include "libpq/auth.h"
@@ -2060,6 +2061,17 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_saop_binsearch", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables the use of binary search when processing SAOP conditions."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_saop_binsearch,
+		true,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -3400,6 +3412,17 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, assign_tcp_user_timeout, show_tcp_user_timeout
 	},
 
+	{
+		{"enable_saop_threshold", PGC_USERSET, QUERY_TUNING_GEQO,
+			gettext_noop("when to use bloom filter or binary search."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_saop_threshold,
+		8, 0, INT_MAX,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index ac4478d060..feddde264d 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -17,6 +17,9 @@
 #include "executor/nodeAgg.h"
 #include "nodes/execnodes.h"
 
+extern PGDLLIMPORT bool enable_saop_binsearch;
+extern PGDLLIMPORT int enable_saop_threshold;
+
 /* forward references to avoid circularity */
 struct ExprEvalStep;
 struct SubscriptingRefState;
-- 
2.21.1

0003-bloom-filter.patchtext/plain; charset=us-asciiDownload
From a0e049aaceefd5e288d3c0e4b066cdb01c6985ea Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Fri, 24 Apr 2020 15:47:38 +0200
Subject: [PATCH 3/4] bloom filter

---
 src/backend/executor/execExpr.c       |  46 +++-
 src/backend/executor/execExprInterp.c | 300 ++++++++++++++++++++++++++
 src/backend/utils/cache/lsyscache.c   |  96 +++++++++
 src/backend/utils/misc/guc.c          |  21 ++
 src/include/executor/execExpr.h       |  30 +++
 src/include/utils/lsyscache.h         |   2 +
 6 files changed, 493 insertions(+), 2 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 29a5b06852..6d5356f75f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -947,12 +947,16 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
 				Oid			func;
+				Oid			hashfunc;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
+				FmgrInfo   *hash_finfo;
 				FunctionCallInfo fcinfo;
+				FunctionCallInfo hash_fcinfo;
 				AclResult	aclresult;
 				bool		useBinarySearch = false;
+				bool		useBloomFilter = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -971,6 +975,11 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
 				func = opexpr->opfuncid;
 
+				/* Set up the primary fmgr lookup information */
+				hash_finfo = palloc0(sizeof(FmgrInfo));
+				hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
+				hashfunc = InvalidOid;
+
 				/*
 				 * If we have a constant array and want OR semantics, then we
 				 * can use a binary search to implement the op instead of
@@ -978,7 +987,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				 */
 				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
 					!((Const *) arrayarg)->constisnull &&
-					enable_saop_binsearch)
+					(enable_saop_bloom || enable_saop_binsearch))
 				{
 					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
 					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
@@ -988,6 +997,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					Oid			opcintype;
 					int16		strategy;
 					int			nitems;
+					Oid			left_hashfn;
+					Oid			right_hashfn;
 
 					/*
 					 * Only do the optimization if we have a large enough
@@ -1012,6 +1023,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						{
 							useBinarySearch = true;
 							func = orderingFunc;
+
+							if ((nitems >= enable_saop_threshold) &&
+								get_op_hash_ext_functions(opexpr->opno, &left_hashfn, &right_hashfn))
+							{
+								/* Assert(left_hashfn == right_hashfn); */
+								useBloomFilter = true;
+								hashfunc = left_hashfn;
+							}
 						}
 					}
 				}
@@ -1022,6 +1041,15 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
 
+				if (OidIsValid(hashfunc))
+				{
+					InvokeFunctionExecuteHook(hashfunc);
+					fmgr_info(hashfunc, hash_finfo);
+					fmgr_info_set_expr((Node *) node, hash_finfo);
+					InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
+											 opexpr->inputcollid, NULL, NULL);
+				}
+
 				/* Evaluate scalar directly into left function argument */
 				ExecInitExprRec(scalararg, state,
 								&fcinfo->args[0].value, &fcinfo->args[0].isnull);
@@ -1035,7 +1063,21 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				if (useBinarySearch)
+				if (useBloomFilter && enable_saop_bloom)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_BLOOM;
+
+					/* pre-sorted array */
+					scratch.d.scalararraybloomop.finfo = finfo;
+					scratch.d.scalararraybloomop.fcinfo_data = fcinfo;
+					scratch.d.scalararraybloomop.fn_addr = finfo->fn_addr;
+
+					/* bloom filter */
+					scratch.d.scalararraybloomop.hash_finfo = hash_finfo;
+					scratch.d.scalararraybloomop.hash_fcinfo_data = hash_fcinfo;
+					scratch.d.scalararraybloomop.hash_fn_addr = hash_finfo->fn_addr;
+				}
+				else if (useBinarySearch && enable_saop_binsearch)
 				{
 					scratch.opcode = EEOP_SCALARARRAYOP_BINSEARCH;
 					scratch.d.scalararraybinsearchop.finfo = finfo;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f72ff52f02..48a63391f5 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -88,7 +88,9 @@
 #endif							/* HAVE_COMPUTED_GOTO */
 
 bool enable_saop_binsearch = true;
+bool enable_saop_bloom = true;
 int enable_saop_threshold = 8;
+double saop_bloom_false_positives = 0.05;
 
 /*
  * Macros for opcode dispatch.
@@ -182,6 +184,7 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   ExprContext *aggcontext, int setno);
 
 static int	compare_array_elements(const void *a, const void *b, void *arg);
+static int	compare_array_elements_bloom(const void *a, const void *b, void *arg);
 
 /*
  * Prepare ExprState for interpreted execution.
@@ -432,6 +435,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
 		&&CASE_EEOP_SCALARARRAYOP_BINSEARCH,
+		&&CASE_EEOP_SCALARARRAYOP_BLOOM,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1479,6 +1483,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_BLOOM)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpBloom(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3764,6 +3776,281 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	*op->resnull = resultnull;
 }
 
+static void
+bloom_filter_add(PGFunction hash_fn_addr, FunctionCallInfo fcinfo,
+				 char *filter, int m, int k, Datum *seeds, Datum value)
+{
+	int		i;
+
+	/* Call hash function */
+	fcinfo->args[0].value = value;
+	fcinfo->args[0].isnull = false;
+
+	for (i = 0; i < k; i++)
+	{
+		uint64 h;
+		int		byteIdx;
+		int		bitIdx;
+
+		fcinfo->args[1].isnull = false;
+		fcinfo->args[1].value = seeds[i];
+
+		h = DatumGetUInt64(hash_fn_addr(fcinfo));
+
+		bitIdx = h % m;
+
+		byteIdx = bitIdx / 8;
+		bitIdx = bitIdx % 8;
+
+		filter[byteIdx] |= (0x01 << bitIdx);
+	}
+}
+
+static bool
+bloom_filter_check(PGFunction hash_fn_addr, FunctionCallInfo fcinfo,
+				   char *filter, int m, int k, Datum *seeds, Datum value)
+{
+	int		i;
+
+	/* Call hash function */
+	fcinfo->args[0].value = value;
+
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].isnull = false;
+
+	for (i = 0; i < k; i++)
+	{
+		uint64 h;
+		int		byteIdx;
+		int		bitIdx;
+
+		fcinfo->args[1].value = seeds[i];
+
+		h = DatumGetUInt64(hash_fn_addr(fcinfo));
+
+		bitIdx = h % m;
+
+		byteIdx = bitIdx / 8;
+		bitIdx = bitIdx % 8;
+
+		if (! (filter[byteIdx] & (0x01 << bitIdx)))
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it binary searches through the array for a
+ * match after sorting the array and removing null and duplicate entries.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR.  Of course we short-circuit as soon as the result
+ * is known.
+ */
+void
+ExecEvalScalarArrayOpBloom(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.scalararraybloomop.fcinfo_data;
+	bool		strictfunc = op->d.scalararraybloomop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		result;
+	bool		resultnull;
+	bool	   *elem_nulls;
+	int			l = 0,
+				r,
+				res;
+
+	/* We don't setup a binary search op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (op->d.scalararraybloomop.elem_values == NULL)
+	{
+		/* Cache the original lhs so we can scribble on it. */
+		Datum		scalar = fcinfo->args[0].value;
+		bool		scalar_isnull = fcinfo->args[0].isnull;
+		int			num_nonnulls = 0;
+		MemoryContext old_cxt;
+		MemoryContext array_cxt;
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+
+		/* bloom filter parameters */
+		int			m;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		array_cxt = AllocSetContextCreate(
+										  econtext->ecxt_per_query_memory,
+										  "scalararraybloomop context",
+										  ALLOCSET_SMALL_SIZES);
+		old_cxt = MemoryContextSwitchTo(array_cxt);
+
+		deconstruct_array(arr,
+						  ARR_ELEMTYPE(arr),
+						  typlen, typbyval, typalign,
+						  &op->d.scalararraybloomop.elem_values, &elem_nulls, &op->d.scalararraybloomop.num_elems);
+
+		/* size the bloom filter */
+		m = ceil((op->d.scalararraybloomop.num_elems * log(saop_bloom_false_positives)) / log(1 / pow(2, log(2))));
+
+		op->d.scalararraybloomop.num_bits = 8;
+		while (op->d.scalararraybloomop.num_bits < m)
+			op->d.scalararraybloomop.num_bits *= 2;
+
+		/* XXX hardcoded values */
+		op->d.scalararraybloomop.filter = palloc0(op->d.scalararraybloomop.num_bits / 8);
+		op->d.scalararraybloomop.num_hashes = round((m / (double) op->d.scalararraybloomop.num_elems) * log(2));
+
+		op->d.scalararraybloomop.seeds = palloc(sizeof(Datum) * op->d.scalararraybloomop.num_hashes);
+		for (int j = 0; j < op->d.scalararraybloomop.num_hashes; j++)
+			op->d.scalararraybloomop.seeds[j] = Int64GetDatum(random());
+
+		/* Remove null entries from the array. */
+		for (int j = 0; j < op->d.scalararraybloomop.num_elems; j++)
+		{
+			if (!elem_nulls[j])
+				op->d.scalararraybloomop.elem_values[num_nonnulls++] = op->d.scalararraybloomop.elem_values[j];
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararraybloomop.has_nulls = num_nonnulls < op->d.scalararraybloomop.num_elems;
+		op->d.scalararraybloomop.num_elems = num_nonnulls;
+
+		for (int j = 0; j < num_nonnulls; j++)
+		{
+			/* build the bloom filter */
+			bloom_filter_add(op->d.scalararraybloomop.hash_fn_addr,
+							 op->d.scalararraybloomop.hash_fcinfo_data,
+							 op->d.scalararraybloomop.filter,
+							 op->d.scalararraybloomop.num_bits,
+							 op->d.scalararraybloomop.num_hashes,
+							 op->d.scalararraybloomop.seeds,
+							 op->d.scalararraybloomop.elem_values[j]);
+		}
+
+		/*
+		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
+		 * rhs values are guaranteed to be non-null.
+		 */
+		fcinfo->args[0].isnull = false;
+		fcinfo->args[1].isnull = false;
+
+		/* Sort the array and remove duplicate elements. */
+		qsort_arg(op->d.scalararraybloomop.elem_values, op->d.scalararraybloomop.num_elems, sizeof(Datum),
+				  compare_array_elements_bloom, op);
+		op->d.scalararraybloomop.num_elems = qunique_arg(op->d.scalararraybloomop.elem_values, op->d.scalararraybloomop.num_elems, sizeof(Datum),
+															 compare_array_elements_bloom, op);
+
+		/* Restore the lhs value after we scribbed on it for sorting. */
+		fcinfo->args[0].value = scalar;
+		fcinfo->args[0].isnull = scalar_isnull;
+
+		MemoryContextSwitchTo(old_cxt);
+	}
+
+	/*
+	 * We only setup a binary search op if we have > 8 elements, so we don't
+	 * need to worry about adding an optimization for the empty array case.
+	 */
+	Assert(!(op->d.scalararraybloomop.num_elems <= 0 && !op->d.scalararraybloomop.has_nulls));
+
+	/* Assume no match will be found until proven otherwise. */
+	result = BoolGetDatum(false);
+	resultnull = false;
+
+	/*
+	 * First check the bloom filter, and only do the binary search if bloom
+	 * filter says there might be a match.
+	 */
+	if (bloom_filter_check(op->d.scalararraybloomop.hash_fn_addr,
+						   op->d.scalararraybloomop.hash_fcinfo_data,
+						   op->d.scalararraybloomop.filter,
+						   op->d.scalararraybloomop.num_bits,
+						   op->d.scalararraybloomop.num_hashes,
+						   op->d.scalararraybloomop.seeds,
+						   fcinfo->args[0].value))
+	{
+		/* Binary search through the array. */
+		r = op->d.scalararraybloomop.num_elems - 1;
+		while (l <= r)
+		{
+			int			i = (l + r) / 2;
+
+			fcinfo->args[1].value = op->d.scalararraybloomop.elem_values[i];
+
+			/* Call comparison function */
+			fcinfo->isnull = false;
+			res = DatumGetInt32(op->d.scalararraybloomop.fn_addr(fcinfo));
+
+			if (res == 0)
+			{
+				result = BoolGetDatum(true);
+				resultnull = false;
+				break;
+			}
+			else if (res > 0)
+				l = i + 1;
+			else
+				r = i - 1;
+		}
+	}
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararraybloomop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/* Had nulls, so strict function implies null. */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* Execute function will null rhs just once. */
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			res = DatumGetInt32(op->d.scalararraybloomop.fn_addr(fcinfo));
+			result = BoolGetDatum(res == 0);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
 /* XXX: Name function to be specific to saop binsearch? */
 static int
 compare_array_elements(const void *a, const void *b, void *arg)
@@ -3777,6 +4064,19 @@ compare_array_elements(const void *a, const void *b, void *arg)
 	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
 }
 
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+compare_array_elements_bloom(const void *a, const void *b, void *arg)
+{
+	ExprEvalStep *op = (ExprEvalStep *) arg;
+	FunctionCallInfo fcinfo = op->d.scalararraybloomop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) a);
+	fcinfo->args[1].value = *((const Datum *) b);
+
+	return DatumGetInt32(op->d.scalararraybloomop.fn_addr(fcinfo));
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f107f..f7dcf3de5b 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -585,6 +585,102 @@ get_op_hash_functions(Oid opno,
 	return result;
 }
 
+/*
+ * get_op_hash_ext_functions
+ *		Get the OID(s) of the standard hash support function(s) compatible with
+ *		the given operator, operating on its LHS and/or RHS datatype as required.
+ *
+ * A function for the LHS type is sought and returned into *lhs_procno if
+ * lhs_procno isn't NULL.  Similarly, a function for the RHS type is sought
+ * and returned into *rhs_procno if rhs_procno isn't NULL.
+ *
+ * If the given operator is not cross-type, the results should be the same
+ * function, but in cross-type situations they will be different.
+ *
+ * Returns true if able to find the requested function(s), false if not.
+ * (This indicates that the operator should not have been marked oprcanhash.)
+ */
+bool
+get_op_hash_ext_functions(Oid opno,
+					  RegProcedure *lhs_procno, RegProcedure *rhs_procno)
+{
+	bool		result = false;
+	CatCList   *catlist;
+	int			i;
+
+	/* Ensure output args are initialized on failure */
+	if (lhs_procno)
+		*lhs_procno = InvalidOid;
+	if (rhs_procno)
+		*rhs_procno = InvalidOid;
+
+	/*
+	 * Search pg_amop to see if the target operator is registered as the "="
+	 * operator of any hash opfamily.  If the operator is registered in
+	 * multiple opfamilies, assume we can use any one.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == HASH_AM_OID &&
+			aform->amopstrategy == HTEqualStrategyNumber)
+		{
+			/*
+			 * Get the matching support function(s).  Failure probably
+			 * shouldn't happen --- it implies a bogus opfamily --- but
+			 * continue looking if so.
+			 */
+			if (lhs_procno)
+			{
+				*lhs_procno = get_opfamily_proc(aform->amopfamily,
+												aform->amoplefttype,
+												aform->amoplefttype,
+												HASHEXTENDED_PROC);
+				if (!OidIsValid(*lhs_procno))
+					continue;
+				/* Matching LHS found, done if caller doesn't want RHS */
+				if (!rhs_procno)
+				{
+					result = true;
+					break;
+				}
+				/* Only one lookup needed if given operator is single-type */
+				if (aform->amoplefttype == aform->amoprighttype)
+				{
+					*rhs_procno = *lhs_procno;
+					result = true;
+					break;
+				}
+			}
+			if (rhs_procno)
+			{
+				*rhs_procno = get_opfamily_proc(aform->amopfamily,
+												aform->amoprighttype,
+												aform->amoprighttype,
+												HASHEXTENDED_PROC);
+				if (!OidIsValid(*rhs_procno))
+				{
+					/* Forget any LHS function from this opfamily */
+					if (lhs_procno)
+						*lhs_procno = InvalidOid;
+					continue;
+				}
+				/* Matching RHS found, so done */
+				result = true;
+				break;
+			}
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_op_btree_interpretation
  *		Given an operator's OID, find out which btree opfamilies it belongs to,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ff9143a4ab..437d2d4f5a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2072,6 +2072,17 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_saop_bloom", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables the use of bloom filters when processing SAOP conditions."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_saop_bloom,
+		true,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -3692,6 +3703,16 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"saop_bloom_false_positives", PGC_SUSET, QUERY_TUNING_COST,
+			gettext_noop("Target false positives rate for bloom filter."),
+			NULL
+		},
+		&saop_bloom_false_positives,
+		0.05, 0.0, 100.0,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index feddde264d..32ae3b7a9a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -18,7 +18,9 @@
 #include "nodes/execnodes.h"
 
 extern PGDLLIMPORT bool enable_saop_binsearch;
+extern PGDLLIMPORT bool enable_saop_bloom;
 extern PGDLLIMPORT int enable_saop_threshold;
+extern PGDLLIMPORT double saop_bloom_false_positives;
 
 /* forward references to avoid circularity */
 struct ExprEvalStep;
@@ -217,6 +219,7 @@ typedef enum ExprEvalOp
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
 	EEOP_SCALARARRAYOP_BINSEARCH,
+	EEOP_SCALARARRAYOP_BLOOM,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -564,8 +567,34 @@ typedef struct ExprEvalStep
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
 			/* faster to access without additional indirection: */
 			PGFunction	fn_addr;	/* actual call address */
+
 		}			scalararraybinsearchop;
 
+		/* for EEOP_SCALARARRAYOP_BLOOM */
+		struct
+		{
+			bool		has_nulls;
+
+			/* pre-sorted array */
+			int			num_elems;
+			Datum	   *elem_values;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+
+			/* bloom filter */
+			Datum	   *seeds;
+			int			num_hashes;		/* number of filters to compute */
+			int			num_bits;		/* size of bloom filter */
+			char	   *filter;			/* bloom filter */
+			FmgrInfo   *hash_finfo;	/* function's lookup data */
+			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	hash_fn_addr;	/* actual call address */
+
+		}			scalararraybloomop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -745,6 +774,7 @@ extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+extern void ExecEvalScalarArrayOpBloom(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c9c68e2f4f..852262c70b 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -80,6 +80,8 @@ extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
 								  RegProcedure *lhs_procno, RegProcedure *rhs_procno);
+extern bool get_op_hash_ext_functions(Oid opno,
+								  RegProcedure *lhs_procno, RegProcedure *rhs_procno);
 extern List *get_op_btree_interpretation(Oid opno);
 extern bool equality_ops_are_compatible(Oid opno1, Oid opno2);
 extern Oid	get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
-- 
2.21.1

0004-try-using-murmuhash.patchtext/plain; charset=us-asciiDownload
From c4f8bd137ab8fe498237e499e9384f3ea7dd45b1 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tv@fuzzy.cz>
Date: Fri, 24 Apr 2020 22:28:46 +0200
Subject: [PATCH 4/4] try using murmuhash

---
 src/backend/executor/execExprInterp.c | 71 ++++++++++++++++++++++-----
 1 file changed, 59 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 48a63391f5..45e0954fde 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3776,28 +3776,74 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	*op->resnull = resultnull;
 }
 
+
+static inline uint32_t murmur_32_scramble(uint32_t k) {
+    k *= 0xcc9e2d51;
+    k = (k << 15) | (k >> 17);
+    k *= 0x1b873593;
+    return k;
+}
+
+static
+uint32_t murmur3_32(const uint8_t* key, size_t len, uint32_t seed)
+{
+	uint32_t h = seed;
+    uint32_t k;
+    /* Read in groups of 4. */
+    for (size_t i = len >> 2; i; i--) {
+        // Here is a source of differing results across endiannesses.
+        // A swap here has no effects on hash properties though.
+        memcpy(&k, key, sizeof(uint32_t));
+        key += sizeof(uint32_t);
+        h ^= murmur_32_scramble(k);
+        h = (h << 13) | (h >> 19);
+        h = h * 5 + 0xe6546b64;
+    }
+    /* Read the rest. */
+    k = 0;
+    for (size_t i = len & 3; i; i--) {
+        k <<= 8;
+        k |= key[i - 1];
+    }
+    // A swap is *not* necessary here because the preceding loop already
+    // places the low bytes in the low places according to whatever endianness
+    // we use. Swaps only apply when the memory is copied in a chunk.
+    h ^= murmur_32_scramble(k);
+    /* Finalize. */
+	h ^= len;
+	h ^= h >> 16;
+	h *= 0x85ebca6b;
+	h ^= h >> 13;
+	h *= 0xc2b2ae35;
+	h ^= h >> 16;
+	return h;
+}
+
+
 static void
 bloom_filter_add(PGFunction hash_fn_addr, FunctionCallInfo fcinfo,
 				 char *filter, int m, int k, Datum *seeds, Datum value)
 {
 	int		i;
+	uint64	h;
 
 	/* Call hash function */
 	fcinfo->args[0].value = value;
 	fcinfo->args[0].isnull = false;
 
+	fcinfo->args[1].isnull = false;
+	fcinfo->args[1].value = (Datum) 0;
+
+	h = DatumGetUInt64(hash_fn_addr(fcinfo));
+
 	for (i = 0; i < k; i++)
 	{
-		uint64 h;
 		int		byteIdx;
 		int		bitIdx;
 
-		fcinfo->args[1].isnull = false;
-		fcinfo->args[1].value = seeds[i];
-
-		h = DatumGetUInt64(hash_fn_addr(fcinfo));
+		uint32	v = (uint32) murmur3_32((const unsigned char *) &h, sizeof(uint64), (int) seeds[i]);
 
-		bitIdx = h % m;
+		bitIdx = v % m;
 
 		byteIdx = bitIdx / 8;
 		bitIdx = bitIdx % 8;
@@ -3811,24 +3857,25 @@ bloom_filter_check(PGFunction hash_fn_addr, FunctionCallInfo fcinfo,
 				   char *filter, int m, int k, Datum *seeds, Datum value)
 {
 	int		i;
+	uint64	h;
 
 	/* Call hash function */
 	fcinfo->args[0].value = value;
-
 	fcinfo->args[0].isnull = false;
+
 	fcinfo->args[1].isnull = false;
+	fcinfo->args[1].value = (Datum) 0;
+
+	h = DatumGetUInt64(hash_fn_addr(fcinfo));
 
 	for (i = 0; i < k; i++)
 	{
-		uint64 h;
 		int		byteIdx;
 		int		bitIdx;
 
-		fcinfo->args[1].value = seeds[i];
-
-		h = DatumGetUInt64(hash_fn_addr(fcinfo));
+		uint32	v = (uint32) murmur3_32((const unsigned char *) &h, sizeof(uint64), (int) seeds[i]);
 
-		bitIdx = h % m;
+		bitIdx = v % m;
 
 		byteIdx = bitIdx / 8;
 		bitIdx = bitIdx % 8;
-- 
2.21.1

#10James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#8)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, Apr 24, 2020 at 5:55 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Fri, Apr 24, 2020 at 09:38:54AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 10:55 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

Well since it has to do preprocessing work (expanding the array and
then sorting it), then the number of rows processed matters, right?
For example, doing a linear search on 1000 items only once is going to
be cheaper than preprocessing the array and then doing a binary
search, but only a very large row count the binary search +
preprocessing might very well win out for only a 10 element array.

Hmmm, good point. Essentially the initialization (sorting of the array)
has some cost, and the question is how much extra per-tuple cost this
adds. It's probably not worth it for a single lookup, but for many
lookups it's probably OK. Let's see if I can do the math right:

N - number of lookups
K - number of array elements

Cost to sort the array is

O(K * log(K)) = C1 * K * log(K)

and the cost of a lookup is C2 * log(K), so with the extra cost amortized
for N lookups, the total "per lookup" cost is

C1 * K * log(K) / N + C2 * log(K) = log(K) * (C1 * K / N + C2)

We need to compare this to the O(K) cost of simple linear search, and
the question is at which point the linear search gets more expensive:

C3 * K = log(K) * (C1 * K / N + C2)

I think we can assume that C3 is somewhere in between 0.5 and 1, i.e. if
there's a matching item we find it half-way through on average, and if
there is not we have to walk the whole array. So let's say it's 1.

C1 and C2 are probably fairly low, I think - C1 is typically ~1.4 for
random pivot choice IIRC, and C2 is probably similar. With both values
being ~1.5 we get this:

K = log(K) * (1.5 * K/N + 1.5)

for a fixed K, we get this formula for N:

N = log(K) * 1.5 * K / (K - 1.5 * log(K))

and for a bunch of K values the results look like this:

K | N
-------|-------
1 | 0
10 | 5.27
100 | 7.42
1000 | 10.47
10000 | 13.83
100000 | 17.27

i.e. the binary search with 10k values starts winning over linear search
with just ~13 lookups.

(Assuming I haven't made some silly mistake in the math ...)

Obviously, this only accounts for cost of comparisons and neglects e.g.
the indirect costs for less predictable memory access patterns mentioned
by Andres in his response.

But I think it still shows the number of lookups needed for the binary
search to be a win is pretty low - at least for reasonable number of
values in the array. Maybe it's 20 and not 10, but I don't think that
changes much.

The other question is if we can get N at all and how reliable the value
is. We can probably get the number of rows, but that will ignore other
conditions that may eliminate the row before the binary search.

I'm not trying to argue for more work for myself here: I think the
optimization is worth it on its own, and something like this could be
a further improvement on its own. But it is interesting to think
about.

I don't know. Clearly, if the user sends a query with 10k values and
only does a single lookup, that won't win. And if we can reasonably and
reliably protect against that, I wouldn't mind doing that, although it
means a risk of not using the bin search in case of underestimates etc.

I don't have any hard data about this, but I think we can assume the
number of rows processed by the clause is (much) higher than the number
of keys in it. If you have a clause with 10k values, then you probably
expect it to be applied to many rows, far more than the "beak even"
point of about 10-20 rows ...

So I wouldn't worry about this too much.

Yeah. I think it becomes a lot more interesting in the future if/when
we end up with a way to use this with params and not just constant
arrays. Then the "group" size would matter a whole lot more.

For now, the constant amount of overhead is quite small, so even if we
only execute it once we won't make the query that much worse (or, at
least, the total query time will still be very small). Also, because
it's only applied to constants, there's a natural limit to how much
overhead we're likely to introduce into a query.

James

#11Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#9)
2 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, Apr 25, 2020 at 12:21:06AM +0200, Tomas Vondra wrote:

On Thu, Apr 23, 2020 at 04:55:51PM +0200, Tomas Vondra wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

I think we usually use something like 64 or so in other places, but
maybe I'm wrong. The current limit 9 seems a bit too low, but I may be
wrong. Let's not obsess about this too much, let's do some experiments
and pick a value based on that.

Another idea - would a bloom filter be useful here, as a second
optimization? That is, for large arrays build s small bloom filter,
allowing us to skip even the binary search.

That's an interesting idea. I actually haven't personally worked with
bloom filters, so didn't think about that.

Are you thinking that you'd also build the filter *and* presort the
array? Or try to get away with using only the bloom filter and not
expanding and sorting the array at all?

Yeah, something like that. My intuition is the bloom filter is useful
only above some number of items, and the number is higher than for the
binary search. So we'd end up with two thresholds, first one enabling
binary search, the second one enabling bloom filter.

Of course, the "unknown" variable here is how often we actually find the
value in the array. If 100% of the queries has a match, then the bloom
filter is a waste of time. If there are no matches, it can make a
significant difference.

I did experiment with this is a bit, both to get a bit more familiar
with this code and to see if the bloom filter might help. The short
answer is the bloom filter does not seem to help at all, so I wouldn't
bother about it too much.

Attacched is an updated patch series and, script I used to collect some
performance measurements, and a spreadsheet with results. The patch
series is broken into four parts:

0001 - the original patch with binary search
0002 - adds GUCs to enable bin search / tweak threshold
0003 - allows to use bloom filter + binary search
0004 - try using murmurhash

The test script runs a wide range of queries with different number
of lookups, keys in the array, match probability (i.e. fraction of
lookups that find a match) ranging from 1% to 100%. And of course, it
runs this with the binsearch/bloom either enabled or disabled (the
threshold was lowered to 1, so it's the on/off GUCs that determine
whether the binsearch/bloom is used).

The results are summarized in the spreadsheet, demonstrating how useless
the bloom filter is. There's not a single case where it would beat the
binary search. I believe this is because theaccess to bloom filter is
random (determined by the hash function) and we don't save much compared
to the log(K) lookups in the sorted array.

That makes sense, I think the bloom filters are meant to be used in
cases when the main data don't fit into memory - which is not the case
here. But I wonder how would this change for cases with more expensive
comparisons - this was using just integers, so maybe strings would
result in different behavior.

OK, I tried the same test with text columns (with md5 strings), and the
results are about as I predicted - the bloom filter actually makes a
difference in this case. Depending on the number of lookups and
selectivity (i.e. how many lookups have a match in the array) it can
mean additional speedup up to ~5x compared to binary search alone.

For the case with 100% selectivity (i.e. all rows have a match) this
can't really save any time - it's usually still much faster than master,
but it's a bit slower than binary search.

So I think this might be worth investigating further, once the simple
binary search gets committed. We'll probably need to factor in the cost
of the comparison (higher cost -> BF more useful), selectivity of the
filter (fewer matches -> BF more useful) and number of lookups.

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

test-saop.shapplication/x-shDownload
saop-expensive-cmp.odsapplication/vnd.oasis.opendocument.spreadsheetDownload
PK"a�P�l9�..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK"a�P�u����Thumbnails/thumbnail.png�PNG


IHDR����'efPLTE###+++555;;;CCCLLLTTTZZZdddkkksss{{{����������������������������������������������������/�IDATx����n�8`�47|�`��y��\���u��^�G��G�u0���F�L�F��N�d�]U��KU��MC5�O6���=��oU���h��n2���/#5������O��s��Zo���0��q�'��k)��n'�y|���pV�ds��p���57\�z���������+������.t/�6�������~P�J^�43/�^��B�\�R�����t�>�[;Z7����^�������������*��S}�7�W�2�[��~����z�������bP"Gm_�{�r�]�o�9����wt�K1H1��Kb$��������v �P��R��t�+^U������.��[��
��y�_.�3���6���������>%R�)��m~����P�����W���_m�!5��=vE[���G<��5��_6����=?g��7MU-��r���V��sr(�k?���Dq?��u�-����z�T���;�_S�~2z�

�-��'������_�c�I�C_�g9W�}K��m_����Sn��v������|�������h�Y����\�_�V�T?��3��[�YJ���������tX���u�~���*��]?�p�2��X�^,E7]�����Y
�+����:�H#�e�/|���tO�8�.M6.�>S6�����1.��t���W�}�����Z���z���z���z���z���z������T��������"��L+��(
�F��^=�Q����]H�|�U�_�AoU?f��q�z#��l���\=��C=��C=��C=��C=��C_���������v?���5�v�J�_ZT�3}')�����qE��9�<�e�z����}���%}�3�*� FQ0�e�>�~_�g��>��7�������"}�1RZN��h��yEu��k�1�C=��C=��C=��C=��C=�T�SJ�x?��*������}�F�|�>w3M�]��D�6��F����n�^���u����{1mSi��6'���C=��C=��C=��C=��C=�����5�;�����������+���)��������]�������:�xI���6���<R���Nr���h�c��z���z���z���z���z���A=�����Jb5��0T������J���g1eF�nS/z6�6��t���4n�[7�y���m����Ly�c���z���z���z���z������U�����Z�w��f@�|�~L�\���
�]��y9������
�Kz��^P8mS��1���2o�r��~R���[s��=��C=��C�?��sX����&�IEND�B`�PK"a�Psettings.xml�Z]s�8}�_��=�#)�@��d�-�0�d��	����I�I��6d�cS�X��O	�u����~H�\y��BR�=�y��j�t)_�����������9������N�WG�������e7�Y��]$��.'��r���L��|��%OV�����T����a�'�(�f������W�s�(J����
���	�11Y��8�'������iY��6_��&H�Q^����qdZ����G
�����y�s���3d���TO��\Y��y�5���G0Wf����ZfA�>��Z{�_],3M?i��u�E	�<�Q��
�4�����OEL���M�)�����"�o�4M�9%�%?3t{J����>�D1FI����,_��[�m�o��X�/���(���0�gT��.���D���A(��BOY��P�;��������O_�������[q���N�B����F�
�*�^�����F�T��=aA56��(���(��Do����!h:h�n�������v&�SX�[]V��#X�)�kN��!�x�;1�e���&t���f��~H�tpH�tpH[� o8n(�������e���r�@�fD�����v������\��a, jAK���w�/��:�T�����u�E�\Z��V�C�:gkY
8��Hmy��	��@\fH�%��a����
��'o�L��N����������/y5s���&��6'�'D*H��
�X������Wn[�l�^2����{�=��`��G*s��<����I�+*�']���G�J�"g}����U�?y��-����t��|+]�P_]���@����n�f��`��F~Z���K"�D�w�SrVF��u���$�;�����]5����gC�`�����WB��u��7���A�X���;* Uxl��D�=)@F����|���#�'���|�P� 1X����E��+�o���B1���y}��������u���1��sP���zD����lw9s{�,(�*�4�%ww��7�
���;���jA|h�����h��^���I���r�?l���l�h78	s3E�:��������Hh�^PNt&,�\��gm��� �r7��*�v�����(b ���p��'�D;��+�_�������?PKV*
�](PK"a�Pmanifest.rdf���n�0��<�e��@/r(��j��5�X/������VQ�������F3�����a�����T4c)%�Hh��+:�.���:���+��j���*�wn*9_��-7l���(x��<O�"��8qH���	�Bi��|9��	fWQt���y� =��:���
a�R��� ��@�	L��t��NK�3��Q9�����`����<`�+�������^����\��|�hz�czu����#�`�2�O��;y���.�����vDl@��g�����UG�PK��h��PK"a�PConfigurations2/floater/PK"a�PConfigurations2/menubar/PK"a�PConfigurations2/accelerator/PK"a�PConfigurations2/toolpanel/PK"a�PConfigurations2/images/Bitmaps/PK"a�PConfigurations2/popupmenu/PK"a�PConfigurations2/statusbar/PK"a�PConfigurations2/toolbar/PK"a�PConfigurations2/progressbar/PK"a�Pmeta.xml���n�0��}
�d6 `���*U+uRu7��uk��1a��S��E$6>����P>�[�
�I�+GB3��n*�������P��������.l�����;2�*�[M�dG4mEG#�$��B�4�F�rVR���O�N�a�a���(
��+���;�Vy�3(��:G1\�)�{CM�6�1�b4�sho�J��^��r�nm`dwpLH
_��@�ls����N1����Z:IU�����zoZ�������&27��8:]E�F!JB���IS�<DY�')���e�uG�y����_��$��bW�p�&�v�Ma�&�X7!��7�����	���xS��b�]���yf����O�h��0�p�G��I��|��g�,	6��d�/��x_�{�x����7��<���;'Y�uG�J����U�YdB�U�r��'����Kxu����V�PK
�l ��PK"a�P
styles.xml�Zmo�6��_a����%�i{���m�mP����`$J&J�E�I���(So���I7�hb����s/$e����t��� ,;s&c��,d��3����{��/~8eqLB<�X�Jq&�B�R\�@8+�z��Y�l�PA�y�R\�E8g9����F��RzD)*�����7b����d�������8��X����P����1sC��H��7�d_������6��x33�x��l��������+N*
=L�\��&��g�)h�}k����k�S�jE5���+�r�"[��_�dpv����%���L���2����4�eS$�=�=��������m^�t�Z[�*�$��F�����T)��]������[��N����w�CD��q�v����e�4�N�j~�q�3.*C����	�R]������5��GQ'��zP�P4�����Z/����S ������T%�n�-O�� f�,�e���79�DN!���5
v�P���r�4�+��T[�R����pII���%]������P\}���+w���:�f8�s�v����pH����x��H��F�9��O���� I�-hJ����#�Y�k��QM���	��k(�bC��������'*7������Dj���FJ@���N�3o��G��Qz���C�4�G����9�R���hE����\��J�
1��������|�����g2=h��r7"�@�<�M�s��M&m[L����1�S�%+��,��@e*8X����T�BwDY_(��3�oK3S*6�/���>M�MN����K�\����R��#�S}�)�6�i�.}���R����\D"
hj��&>��h{��������H��w�����n}���Qy@�^���RC� �@Ap�������n�7�$K����FV� ���K�� �s�n�b4��w����ON��{9����n��g�>8��W`��zG�\2��������$��&�py�q
�1��pE��Q����Kn2��0]�I��G����_A��
F�/�8��O���8U�V���1�=��~��������P��`��|�1Ys�+`�NH?e#6�1=�
��=��A��/���*�*U�0���4k�CU�CJ����;��!K�����S�R6�!��fON�����D�1��0��uI�?�g��$��|W���������!����A���l�[���se8�[;n����$m�\(�x�\�oyli��j���w�xm�8�>+m���M���H��;������J."���OJ��R��B�j��>���z�N����&�a��z+#�Z�dn�V\>����Q�q����d�)D��f�C>�V)����!������l�Pz�G��[y�H� �k&���`[����t�t����O�����,9�]q��w�Q�|�V��p5�_u����e���88.{G�xS�0����7F���C�Wc����2����,���n�����������_���l���:��(�_���/'F��L����AR`��(-O���}�uwV{����HQQ��zX9(5�z�o�AG���/N��X��w��X�����^s��$4"/�X���}HsS��A�R��f��0��5�2���������]�~,?���l�b�l�	a:��K��?������?���XR���"�s�aJ~
��g/��]���������w�#7x�,|�S�|��B�������������4���|�e�l���b6����r���~��{s�PK�|z��w'PK"a�PMETA-INF/manifest.xml�S��� ��)��%���"M{(�t��c*�Qt,���	M�e)4��:��7���{W� &��a��U�����}��O���6�Bk ��U����mX�(�J6IT=$I��P�6��$�������	X�+��p�pc'' �3jE��J��%��7�� g#�vU�-��.��r`�suPtl�x��h�j�h�
��v$N����[�)DP:��%R�����.	��<`�@��Ub�/bI@T��xI�2� �P^<���������{��v9�i-��R�8����v��� �zyj�.^?�b�����F����_PK�u��1,PK"a�Pcontent.xml����\9�&��E��o>%��D�v�<��f>�;X��],0�vV�q��`������"�.�3�G�Q)�i���r22#�Q�������?�����w�?�z������������w/_������?��/��������O�~�����_�{�������x��c������~<~����������x�������}������r����~������:�����|]�������x����/��x����������~���o�/���M���?��}�?>�~����������W_=�?^�z���o?���?���o�o��{����?����_|������V/_�p��.��?�?|�}s��y��e�/���o�z����y����O���w�Ir7/����k�����s�������������^g�?.zY�T����}����
����o���/�������ojW���[����_��<Z��w��}~���#����������o��������_��x�����/>�����{����,���=/�� �o�������?xY����������7�7~�n���������y�?�������_��������>��O??��>�y]���L~���Y��8�C
#	�������������z�F_��_��l�����=���������_��<����q��_�����z�������\���5������O�C�u���?�t���{����������_������7���qy��{���?�+�����>��l����!�Y�6��p�M_���_>��X������S���������w/^����1��?w������__�
���~����MHK�����W������������|ew����������~�{���.>�����?X�����P����������G�����zy�m�����?���������y�~�x���s����������<�'�n��C��?���_?�K[���?���8|��G/���_vz�C�H;��_�����+���g��u~�������������{���z��|���W��'��^���k���_>����y��p�g�hy���7�����z��/_>��[��v�������H~W~�_?�={���Wo��~���?������?>?�����M���?����w�N���������S_8tL�_�������<=�����"�����{�~����>��������{�j�����|�����w��J��������.�i���7��>;�����ws��9y�����>��B~���s0����_������YX�w%p��~��;�<��{��8�����������������K���^���|�����\����sj��_>~�������;�����w?�5�X�����������Q=��J���_������{��O/:�N_���9���������=�����o0�[~�wk�Z�=��C$L��������g�����g��L�n�����~+����R�uz�����O����2�U�,��^�C�P�^iw�py4���^��N���_/��^y�%��
1/�^���������CB�|,��y�z%�=w�-o�k�r���2o��O�o����v�u��U��f.�oZ���;�:��A�zT��{��l��*�W�_����m������?n��m�������l��m������Eo�����m��m�����s���A���+�hP��A���+���x��+?(^�A���W~P��������x��+�xP��A���+�xP��A���+�xP�
��U���x��0(^�A�*�WaP�
��U���x��8(^�A�*�WqP����U���x�+�dP��A�J�+�dP��A�J�+�dP��A�J�+�tP��A�J�+�tP��A�J�+p��A���nP�7(h���
[��-p��Q������/�K�:��Q�����/�+w:��Q������/�&:��Q��Q�����\���0J����0J����0J����0J���0J�$�0J�D�0J�d�0J���0J	���0J���0J
���0J��0J�$�0J�D�0J�d�0J���0J���0J���0J���0J��0J!�$�0J#�D�0J%�d�0J'���0J)���0J+���0J-���0J/��0J1�$�0J3�D�0J5�d�0J7���0J9����������Z7��CJ��`��o�h�e����;���wc�������{�C�������H�SU���X`T��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��0l���	�F8��0l���:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8��G����:U����XpT��c�Qu,8j����@��18j����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����(
=����e5�?����W/�~|���wo�|��h��/:}���^���|�������vw��/:<��/��~�����wo�}�������?|w���?��Wo_>{���w�?�������p�����w�����~��gw�H?������Y��������L�����C���7_O�G�^�����_���9|��={����o��1}�>����7��~��L��WQ�������w�O�����=�x���������F\�����
O��W���nQ��������������5�~���K��`���=���_����>A���2N������������/�_����?�����V>o~���?�=������������^|��Y~�������������w���#}������/���i��1}�����O�y���|�������=Z~�&~��U��J�������I��vX������=�K=�O��=������YIt�����
��O�U��>�[�l����a�(�,��C&��.�vx<�;�������o8��w����d��x?�{�&�������~���Xn���?���y�1�{�"V��|w���N��w,�|��/������o���)zG���C�?;/�=����s7$������ =������_�"���..g�����hz��os�+������_�Nj�
D!���zUKTp�Y��,!q�8����%�Z�U�Y�H,v���/��,�^W�W��<H����y\���W�
W��A=�-��N���ri������k��)5���[<<.����,�6�����F��Xn,�����t�;����J�������� �����'�w8�z>�����>�j~M�C
�$j�*�X����H
MQ����
>!�s��g6��mG�O7f���������:08��>�D��I���7r��X2������b���8�!�V�F���0`=|j��F�4��{��W�����r������~�?V���J�;wQ�I;� ��}=b����`�����Bo��I��I+w!��f�I"��Ft�.e��Yn�`��Fm�������EB�Qn��`�@jI�'���R�Xb&�'.�t��V?^2���a��A{y.�3��;�e�s>�@��,�p~	+'�m^�n�*��"SRL�Kh�.�yu���ct�`�SHKp�yz<����~p�V�=���D�s��j�
�GT�F/���Z�/�%k\���()�{�z(��`�9INM�E��CTf�(n�G]��A������kJ��j������( hb�R��$H�F)^K��s����.��v�g�Tl�&-���v���g�WUA��gb���ND�<I[.F��#X��bz<��{�l�?��7K��Q���j9��4	��
�a�#]n�K^�n�� 	"��1�@��o���W9�{@'88q��/����}��ggW]�M������\���#y�Ii��X���@��DT��%Nt���!� ]=�z ] �!���t
�Y6�\����V�j�`��N8}�`�C�Cg��7E���~��
h�7�D�+��R��\I�x��vA! ��;,2���E8������'���B}!-i;�F�tZ��tD�3T[��,;<\,�G�o����1"��D��Zb[ac�W�[����J�&��v9j�xu��rt��G4}V��S���h��u4Iu(N�]iu~V^���bp�>��4���J�x���|���i
=��)���ZPz��_OZ�.�e��k�(�\�S�1���]��1�)����s�[Xt���o�1�@2���rn���a{��b����Z�x�$��NI�rd��b�����RpS��'���A��F�G����"T+F���������L;<��<5m�3(�����n�EZ���W"�6�n���a2j:��,t*-�R��m�`m^�4y�A(ml��2�}��i���3(���.J<����h�$���6�P1��B=�.�RX�"F[���B��e��������!X����G�
N' ���K����U#c�VH���8���0�3���������t���30����v����]�j��t)�5r��������ej�H�P����-����V2������L6��xZ�>�zZli�!�W��{��{���6�n��:����fP��0;�v�n��"���1x	��y������7�Z�`_�
�K��:K+���{BH�L��X�����������>0k����Uu�����F�A48�MI��,�v�
"�t�RP��Ce�y��6R�����v�t<��b�{I~�e'���"~&<t9�t	�!<DU�����7��)<H�����C�����9g�:
�0<�x�j����������!�R7dT��_v��W�t�t���+q?tTSS�%bv�F��r����8���m�*�]����i����yt=�5�n��b`"(����{�������]��b��3��u��i	����Vh� ���|��,W��]�j�����cg��)�{��������q}���Q�/u�+�)�q���T�H#c*���n?����x��/�+[#��qMO�������iq�%��twMK����,wg5J�����:|�EN������J^\�����w���_������m�WY�H���|Q��e���g���&�X9y�q���x�xTW�g)���K�zdg��=���=��CIii(u�(���G���Wm(���H��S�_����T�]����#}6����	���RDS��.�G���W�;�SN��Asf�x�i0z����r����4�s�}��9��b@I�>�hV��U+��STc�X"�����>yj&kV]��v��G7y�������6�M��	���������"���4���s�D�O���W�[�� ��k�d�_A�n�^���NQ���
�{<�G+
)�������g�,Wm��R&p�,-�E�ph�vz'�1������=>NQH�z����= �]3#��]�Q-' \b����� (����$�tY�:y����Z���M��mf�v=l�v��}�;E,��i��2�n����WA��>&��R||�jk�0����_�{�d��d$�7�}��X�ZAK��g�����������)_��<:�L".�%B�����S`�J�-��z�ua�W���{��[��u�c��~����9�����}�B�6uT�?�����R��9X�����(-���\��
�@A�S�f���xD�U/���t��v������"[r��*~��TBs�"^�������"��\$R5]�Lg�>oH��G	'�Y
>S�ez��S�tP�H�@����O4@�����X���NEv4�]����Y��������>�|�A9�j}C��� �Vq!�H����
�6�n�x��z��$�
������w���xuc�*������:Rjfx>�������x�XU#1@m���]�`uV^���H\t�Y:��$�_q�
���Y����2��/��"�cIkw��tJ;1��O�%O��T��^���� �I�|t1�Q��VP;�)�
�q��\-��Mg��n�t9mL1_(�%����tu���!%����.~�����B�:��D#����g�����������9���94�%��i���%���rZn����I���C&
Y��Nq�����8KAx/���%Y����U��c �hs�>��#��
G�P���:��~[u�V^���\�vP�R��0�F t�������0�j���j
��R9���Jm��t���B�i�J�q�2Yo��6�R�;,y�������oS:r8{��F�������?�#DK���.�V�j?���8`�����nj�n��B1�3�a[��M���Pqv����M�&Q|�A5�v�)�H������|�
�e1jCP���wI�J^�n�*p 
>�e���
��9�xuc�*���@�~Fdl�v�izMMN�\��������]�`uV^���L��A�xC�A�E���U+SZ	�\:\���f��85KrAB��
}�� �I	Y�G�� ��\��x��������?c�r�D�|[��d.U_��|2��q�	}`��(��&�S�)5+^5b�{jI}�j ��W1xb�HX��u���t_�H�?���������g4pn	r6���QU�
m�b��q��2y��b�W�[����T����������d�I�
Kf��#M�j��i��@|4��H���z�
(E��4��l��%�b�m��6��G��#Z�w�a����y����:���$
��Y���|�0����x��Q/�B:I��K���F��a��)������������#u���`Sx`$��E�t�!<����9��H{�����d�gY��Y�OSv���zaI��M�\��%���{)����)�C�/%�'������`�����)v�J^�6{���^`p��
��4]���xu�������x0Mu�����+MN�\���U�;����N
����A��%���%��O����@�y��/����+��=R��T�;�t�v��Q��Q��AXm5����}P�v�"����]��ewui���9��4�+7g�����S2���.z�������p$u����yl��^�3b���T�.W�o�N��+;EM����bm���������d�!�h��8*CC���	��
{��(K^�n�*KS�g�|�n���uj��������p`��T�mZF�e���3O�p��<�Z�@�.
d�����9n@)3�s��
�}
V�jDi���i:WA��\�M����h�X���,�
Q�4eD(t����T#B�"F���{���i z��|�O��.7���}m�����C /@`8H�4Xg��6�;�L_����V�����l/{��A��{W�]6��-����?�to
%@�{��J���������DJ}�)�P���}������)zu�������"��i���)	_����U�CJ����f��^������kDzHD�
��eX��A�>x�����N���WmDR���G�L-��fzLM�\�Juhg6l��O�� n��@��d�T���h{��U�.H�|�wA�f�����Q
����G���������Nct��>v��WMx��)�%B1e�a�� 6#�M�`+��^5�a8���Pyr4�[5B�U+�������~�l�!�@��8*gcK�(BN9;��h�]S�>�n�������"`���a�.���n�N�L/�x�bb��������V2�X�+6�vY���Yy=s{�IG�@�1��E�E����K��P
�<}�bI��BW��+�����BC�6��By����A�>���������K^���
���c�7��� ���Vg��A�v~l���8����,�>�EzT��y��5��8|1����\_��lm���fO����Z���to��/�������&VK�b^.�9Qn�QRB��E��{K�����&KN�nq�U8P����0��=��k^���Jy�'�(���D����_m��x�e�U-�:�e�.k�:+/Wma�p�R��
<BZ�]F��y�B� ����sK�K���jjr�b�XM~d�I (n�>)��������>��+^������&�EL�-����b@��E��������`�1O"1y�)�����E.��
�o������m�/8�L�Bu2u�>�F>�'^\����q�h��p$�����\%��M}(U��l�!�P�����V�2P��H��-��i�Q��v�W9H�<I
����!.�E������L}���������@�������w/����*�vY��yZy=��f��/$�t�Y�� �
 �r�a��,J����6w��W��RS�{M����o�'CHM;��0}��9��Wmu.�"#������&�E�)�d��{S����q���%�Vt!�b���X��Bw!QV�j��)���J�����qgF��*ojKo��D���E��B�d��.P�'��%��K�`�TA�p���e�����Q`+
m�\��)�p�(H���H�K^�n�J����<K�B�K����n�N�����$���w��?L��M��\��:������i�5X����k��ArO���4�j�5Z��@��sAB�
�T��V��~$��$���@��}�e]V�BIc���f]�j���(��J�����UY����+`@F�G�+��,gA�n@<��7P�uMio���S�;�SO����E~�<�[:���W�{�����:��`��t$_��B�E�l�/�AB���h�!�p�����VZ39N�q�R"`)0�N���W���

9m�������+^����)��#�DV,UN��Z�>�3��JGh����G�.��:M+�g��RA]T������v�'W�jCi<5)�W�b�fR����@������t���2�`:zD�*L^�y��UF��2D��F�����4;�7E���$D����c_:�}�O[�g
Dr@����C�6l��<�AR�-zU�~�;#��4J=�r�D�A��>&�TN��d��#BW��'�}��\�r�h�!��"�r��jC�&Yf��������S�J���-N�c�����L-���w����Uf� DVL���B.��e+��O�T����6`m�������`yClQ�>E?`�HI�S?���`��T�H4J��y]x��
�H�������0la����j�;��8��A����]n�o)�u��4[a=G ��R&����������,a!n	�Q9�3��]�+^���>x/y�o)�:h���<����-1�%��%uI�u�6���Z�h��&,QJ*��n����M��B� ���m�x���Q+�
]��#�!��T�@G��m�����|�"��U"��
4�����v����+���g�E)�Y�&��89}3�OC����9�vY��9dyEk;N}@�$����%t����UN���j�����zy�L�R���{G�IP�R�
0�1��$e
���%��.����j�i��!�(�Q}�uq��e��)���Ci���sS6���|F��>@��������\�������0�=F���$�M�g���������N��M0e�X��m�}����Pw{|4��P�%���{5���z)�))u?����9XP��.�b���`�.�J���M~I�������/��9[b*w�1]��������>��hJ���V����wR���l����t��%K�H$)�(P�h���Dn����D�AX#bt�-d������G*������mH���N(�P�%������+��E��"k~5�0:���B��:�)���o��{��Qes���4��v�G=�a��K�D�i4��[m�G�1���T���g�����iPO����fC,R��A%r��W^�G��(�{lH6��
��*+��O������p
�
��r:����l9`�t��i����&�R�G�A���
0��p�n�:�!��bnMw�������R��.*�����]�iu������@M�Z����@��3������������T�
Y<���O��*��x�z�*C=�t�'��T�����GHg*��SM�W����>~�!�8%h���:����Kp� ��bT&��h�����4��-5�j�Iti�6�hi������u���9m�)G�t�5Ns�x��=��{��Y1�8�/K�v_we:������Rg���`���$�j��l���?Un�����9Z"Q
���w��p]��P�|���M~��\���=��}����u��WN1�OK�8�J�R�6������DS(���>XvY����C�6�	��9U�����S���W1�b���������i���fM.�{�~�������� �"��,j��BRd����0��fLN����Vk�f<])2Nf��Tvr�I�����l����DN[1�6��ys}�j��=Pp����4���^tS�\�����>�=�8���>!G�}�&�m�P����B��}L���	���Q�� �(�p D���)�u��/A�,�Sq,�Y�$�v�������q>�
�L�(,2������8��H
|���H���3o�i���y&y��L���@�z�U��p�@�B+�`i��F����3���z���aRct��b��+:.��=��[�**�F`�,G+�E���|�2.���O�$�i����()�����i���G)��_�;5��N���A����5����T�S�������z���V�����DW�K���d���&)����m;�,�+'m.4��r(20�\b�#Vy�m~�n�+r�>(bK���=U��u��W��#]@"����������M��cO��]VauR����-@��R�������}����D�[U<�����f�?R�I����l;�m"����C��C\�G�U�w��)�h��r�)�Ls��M�#���X�,�d;
�a��"Y��vUj�v�kej}�j��s;ve.���v�y��_������iKo��X���d���$���
�����twZD���9�v�-T�E�����fP�:�������W�B]N-%�n7��y����7���>\��������9�R[��Y`Xd�.W��"������J�����B���X��	��S�!�eUS�!��~5bU�w.w�p�B8�$qq�H��fNF68q%Q��'�v��-H��������-��H���)���-��<
a�]���V�+Op}��l;M�m�5ifZ:�Lq�a��M�u:	N�it���[{���WM����*�lw�����j�zp���Z;[v�+�L��������HPr��BCdY�K�,�u��/��JQ!�z2�Np�.��5�n�J�@��"�F	&�	-�j�+<R�	,1T�'�.��:%{`��MPd�������+A����W#�K;��2�3A�_���Re�} f�i�([����+�r�?��f��_m@L'�c�u9����s����G�IO �I����4��m[���@��g(^�a�-[0�x��s���.i�G8K�����"�������[K�e�1#'�}(���^��2#�K ���m;�.�#t��RC�(��$Pr�N�){����>�@���nt�����S5_���t=Ys�����
c�y��x�4sq��4We��#���S��"Gy?�d�.�:{{`M�6��Cb�k��XaX�RTM�53)6�����*(�P]m��t��&�:O������R����E���X#P}!�j�Z�f^O)HGzbZf�D%�&��)<'�i�m;T�3#���-��w8T��(�b��YX�(><%��ac�aaec,|���L�y��0�)��NU�	:����!�~�W6�=����,������2�Q/�9�����]a�=�����AV���u��/��#!32�T����@�C��_7
~����<k��J'���a���3D�$K������*��X��
�K�L	����$�$]����jb��D������LT�05�s)j�*G�l�b�DM0L{��JM�K:�>����j�j:���"d���>�n����{$���T@�5���� <n@8��'����������*d��1x��D�����3"���T�2W�}�*_x�+KsN���c��:���_��}����_���i�����IQL
-�XSL�EM9|��K/��_���:���)�:0��~	�vw���M�_��x��������` ������u���t������d�e�V�l,i��@!�6�����A�����_mP��^.���`*��<� ����aH�qm���,H�nR!R:_	�j�m�Y����������F�#��:�r�@1}�� ������/��|������4�@��"�����g�������Sp���`W/hf��@�c���]17��Eb�(�d��#F�3+��h�/����S��b����~V����
IJ������Tn�����;R�18��J;�Y���r������
5�F�����c.:
�>9�r�km�N���A9�vY��i�K����Y�	S��4��5��S/�5���H.�d��6���D=!��O.'.��*�������o@���i'�<)
W^����\�������������4E�;2�9�J��+'�l�A>o@~��R~O�Pj*y�q�.��k~5"?������Z�&��v��@�c���1�)��+s�O���N�O'�E������O�[	�����q���eb�$��
-�]�=Xs?;����.�R���M~��@��R"h8���K���k~�4��vUq����d��4y�������v�J��d�e�Vgn���!���������$�v)�[�������L�O�9s#5���V�)�����l����<08(�E9�U]b�S��_�X��s])k0�+~�itb��2\}���X��_��m�	�!Da��WsL1��]�E�\t���0�C�N(�`)����4��Ku������j��qaqR�_����
�0V��=��M���T;}�d�a�"��'�.74��t�IyY���P�'E�n��m[4��@g�����^Z�������$8U�1*Yz��'a���s|{�LiJ����m�uX��>�da1O���0�*�y�L��_mP���G�^��"'-r5P|�2��d����d;
qC�YE����j��>n��� K�b��Y�H��[f<"]K2J�"��h�O���6�GN�0)�H�����[��C'�Q��M�9iO�_�6���'�������T�U�������B]��4�_�#G�}���.y���U�������d���%�q@d��`� c�j�%t9������W���B�Bl���>��U�n�:��|��J��c���4������P��\;�vY��;�+�75:O��e���%�j5����Tv.wiV6D�7������0���X�v����� 5l�R9�A������pne��6�BPpQR�`k��r�Gx��ne�%.J�E~G�iB@��Uc�.��Z-��T.�Y�-i5�������I��s,\�?�'��gZ*\���:r����)��Z��d�?������ �9��t��F9La�C�A\,v>[�z����6��C:���	��B������u��V�%��t�
�,�;�j��9�=R��.R;a�d�eVo�,Y����R`e5t_�N,��_mP�t���I@�kotK���Q�Oxa.5��$>���������Y�!�zA�������=�=����M{"/~���o�#�%��N?�N�|��GD��� ���
w�����|%rD9�pb�{]�"�8��:������=S�����|2�W�"�f��W��vwrDj�L;,>*R#�$�

��\:��&��d�|YBf����F�r��R:�KmX���������P:��N�xSH����9�]�����o��v���P�w��4m�*�����k�*�E�����j�j����|_����t�=O�����0�I:S�����4@�[��$�@�GdS�{�?X��
�N�I%�� K����Y��Mq+��'��F\�mu�
R2�&����GO���Y���j��>~5n���D/������ gF���g�n�� �`RJ�d��
��|��0KG�{��x_������O���/S.'oh�"��h:��,��[�������~!���TY��d��X�K�5�nK���lW=Frl������{<'���G�� �%��G�.+�z#~`��mhT���O���\�c;!EM����=���n���A�c��dAQmS���4X�MXt�����$��3v��Xs�qg�N�($�P-��<`�������jZ�d�*%�\$
������_���$�XR4��2/7��0���n�(�5�K��`��5.��`��������_����k��}v����/����?��������
�=���o��}���wJ�R.M[��p�t_f(�N��'�9�x�-��.S�t�q�p"����6g�����37
��]N]����vg��t�����?�.�sP���#�	 �)�+��S��?6�J��5�Z�J�\�,B�6��������������h:
NqN]t��S��.���_�8E���������'��Lr���b���@{L�hC T�?���Gk~5���F�����������Xd����7JG��O+�TMw�O\����i�e��I�H������>K-�2�jx��u��/%@�������=k��K���M�_���������&��LN�\���a��b]�`�^��r�
0��!y���T�����o�a�/��D�Z�W����T�4������4 [�x��4������x���a��f���#:b�T
�.<M��z�`]fc�Ys2&�d69M���q4�&0�-Ir�1w1�-��C_���5�Z�da/�������h���3��&��"�I�2�����������g��f%n*��G�.����6:6��J�*|�jK���)�u��/v ���x'�n�:�ar�E�\XRj`]��\M���a�C^���?�u=����������]����j�)d��E����y.��fv��^��=��.8��Sp�����V�]�xkn5��~�LKJ���L#/}��.������a�.����������>~5f��5��e�OR��q)6���kM�����x���:��X���`���(�"�"'���4��$��@o�Cr(*�rIV��v�_�]���.��pM��~������YPM�O�9m����9�=V!CuYB6��
���,mb������:o����5�Z��R1�R�|����L4aej��r0t��x�2$�C����S(u!-]/�.�5�QS#��F)	���)������R�j�Gc�N��-��>�	��J��#tY����H�$W�����t�"4�e��$)�",����5�FQ	�'+�#Y�� ~�
B��i��VXZ:�q���Rs�R�]N)%�n���g��x�-�������d������.��c�$�v���1���u���.���]�i�f����������������ks����T��(J,L�^S7�k����]��>(�-y:��o�vr�����_�(Uf�������TT4M��7��L�+�db������O�M��g�id���p�mZ�
��b.�7�ne	�)K�IY�{`���{���
s�������J�������H��I��-m���
���z4,��������5��L1���%Lj,{��U�n��
,.�9��Z�tM��z<��G�+�����i�UX��>�`aCxQ��"��k��u8�n5�5��eT*u-*���+95]r9ju1�vH�:a7l��!H���+I��/VX]�}�j!	A���������(*�����Q�	�	��e������k�
.
{�{JY=�6��������t���S�O�ZX�:+��{K��q���uPnX9��9�G�1�,�Iw�-���"xG�Q�2�P��=������WH�1�P�'p���.���_7m~�K�.��E)���<��4E�/����z%��=������)/���X1+7;}�jAjfJP�9"��j&J��<�^���~zw���	�a��A�CSk���v�����������9��L�v���4���v��iO�i�.�K���z(u�	G��4,���x� �E0H�`�Ke?k�RM�<���\S/��_�����
XRZ�?�%���\�I<�M�P50��[/�>�y���
~�2�"��;�w��_�������J��i��IS�I^�>�����}��c������
�6��Q���K#��Q���Xi�`�WD�>ZL=m�@��=�������{���F�<%��Z��i�������u�Y������#~�cS���f�'��M����m.M��<����a;�W?[��p6�K}+���������&���CD����(��#J:2K$���E����.�t���m~A1�}��}��i�+�������,��4.p5�������x&$������M[�
�S�����������EK[Y�����W�s	��g`����oA)xq���
�]��������'��&XX�,�����u�%�IXB����e�r'���;��R����|�|a����j�)�p�d�&��]��������hc�c�������U�|������x�~�d�!��2�Na�����tDW�����,�K�P���M~)8���T)Z�2*�iG���M�_�{�t��hj�@�_�����
F��,�.d��w��^���� A/���zD�{�Y���Ya<CTGX��VP��i�XNM�\��6a�.jz����P6�0����h�����B]j~��j��w���%�����):d\�@�m�C�N��-��@(��i(CB�t�R\�����!�S&l���u� O-d����6�e��e%%�����?
"���t!��H��Q\��6G���
R��n�]�O���_�����XY��GY=�������\8�>' ���:�Z��'���J�G��.3��2���X���^�#���{S?���1F|��F���|	�DLH��i�?5Q2���a������9'��r��a�K�	�����>H���d���S��*+��=�b�g}m�cJ;Sb
��j�Z��a{v��W��2�s��SGZs�1H7`A�,�=��.|)N��/|I�{�*���/v�(eZ�$����K�1��,���-E��������6�(��i�=��_�2�=��i�+��@�@M�>/5u���b��O�b���pR{��]Vau:����
@� ��eC���@�
@$�{"��o��k�tlj��r0���e6��a�~��9�zJ+��q����"k~5���<M�EH����������%���]��>���6�xa��v�Q�y��re��W����������<������B�I`��-[�r���royI�u�|����L���Y%��M;��XfBN\h���P)����X��S���_���
��w^]�b����+��_7
~�=�(}^��?����������v��+*�!M�,��l���[�J�#�|-3	��� z��F�f�T|:�����?�I`29N��q�Lg�)�-8uN�p_��	�L!����8E���1x�R�~q��4s��2\br��W{������	�%�s)��S^m@J
�����_�uJ@b�����W���j��l��i�������W�v��[�B�jy�l�7��}���`�!�H�T��z!B��P���4y#�T�S��v�_U�K�������B]v�5�n���i!�8T�`k���0��xrR�B��)Pe��i�UX��=�`i!K��)����g���_�@D�pf�������(/I�������40�`��@%���+�%v�.��j�!AJMD��ItOL�o�%/�`��8x4����A�5�������>�5�q���@F&D����T�����'����pW�J=��+29��G�C�ncC����=w��k�
9�p���SJE��,�R�G)b���%�n���!�#���j`y�/���r����r�e��!�W(U��J.��3��v��#y?�$�.��:_{`E�
H��Y��YS,2$6�.���W#R�-�Vvli����f���t�@��N��a��4(�-(M��f}���O(�]&k~��}��.������4�z�@:R��-HP�H�%�N"tK���IS�m ^R���V_��1D��P���D��j��C��x�g^��^��Os}~Y�4C�~�J�{?^:��F�l�;��E��t���+BtRSC�*����B�!+[��"=������WZ��C&S��,� a�!���M�_y�#9��*����`�'yL�'y�{y��{�d�eV��,Xh�����\Z�����.(���[��
� �|�ji�VU?���T#�}���4(�-(1�1�i_�&v�WU�}�jE��)��[:mf��&����1�c�`�/�{�V��n��n�0����
K��_����Q�e��L���40�<�U�#{`W��g���/��tg�� <�$�8#��tQ�D=SR,9����!U�4�� 
��~����M~���N#���a���������ZO,�����2��Z��xZ���	���l�e�Vgk�h��T����u�`����=*V�jEj:^FDMh5�HH�'Jdn��Pb�����48
�8a�DS���T�x�jD���Y��
���.������)������L�p���S6�&�
 ��
M@F����%\z��@H�GL;59K	@X�#���ume���fU:
\+%�G��e$(�*�l�7��{�rR��CH�2�r�SC?*v1��?e~�KPa�g�Y���M~��U��ev����v����g5@O�hJK]N�g���Vx�2.��>���W\��V7�oJI�OG2C.�OA��_�@tJ#�CwZ���L��4�U�0��C�����v���co����\��u?���K�2q"i?�G�w�
8�q]V��/"w8'v�<l�zI�:�\�	Ba�]������W�5� !���i��i�|.�j����U� ��<-�R�i6��-.���B�pw
$�Z�&�v�'T�@�Ib�[Q��`'-'�K�B������'�� �r���l�q�3�y����R�gb"!�Lu��MS
>92PX���d�.��:Q{`E���cp
B�d��s����J�H���p�����p65G2����wd�ip�7�c�Q��)�R>�S�tY��W���KRS�p�j�Qi�i��}S$�������)��x�F��T<&�D��6j�t�
�%�R���2��@�<�`k�R�8��I�x#,�
7��&���%�}u�a2��V�$���b�/�*'�o�XK>W�:���=����m~�n�K0O@Q�(>hu���Ek~�4��+�)��3F4Vd�4��������������]Vau2������cHG2U��&�<����C��!��2�����*�xp�8�%�T>u����l'�	iP��1�4�c��F��+X
M�@WUk3���'+	�zhW6��������V��Q�����;����j����]�]LD��_{�M�O����)-�����c��vo}I��m�]n[-d��V��h�!�p�������b`]V��e�!��]�e���m~y��h�#0�$�j����M�_���s���e(Q��|�]��v��N������J2��J��4l*�j���
K��E�t�[���D��y�j���/�&a��-'1Q��]�M��)n�)C� �W)[����������S^D�����R[w������	$&Z��hf�O�- �r�u45K \z��-@bT[������S�Nm��u3+��%q��q�lw~���Y��tof���G�q%����&�z=�(1�CH�a=���m~�n�K$8v>�G�L��E�\o��u��W�2]$�bNpM��i!N�LN�\��;}\9�h�eV'd,X�D���+&@��� �0}�j"(1)s`4����lO����9�k%��i@��07]�(���`����}��_m t���#F�vE�Y�4r�-2Jg�v�bm���z������A=c�CM�N����j�|��%W��L��Q�r6�*����n>�p�g��Tet8���4����!�^��l�/B�
���"K,�!'E.74}r�18G^�X�nx�.�A���m~p�!w����z�mg���~�4��2@�t���ig��t������QW���,�.9=�vY��9�+Z� ��sT��/]��G��h�>~5"Ur�WM��$
Kg5z��:7NS6^��d�Yp��LA�r������)h���_�8
	�Q�%K�wp�Qj���S.5I����q4�&���y�$@PKk�tB�0�a��������u���:�_�[�'W��]1�91�c�����}�,��Yw�>����@���j��m�#E�%�D���
���c�1�,1f�.U/%�n���� LA�-|�Vu�jt��u��V&�9HdR"TS��[�<|<��)9I���
����m�������%K[��$z%<\|�v��Pt]�*���E���Q��q���F
�b��`:�NC�	��q��	{�$��.�)k~5�0+i$B���zy����2X�����D�%eIZ;��h;Ml�M�c$���)6t�2��Wcl����{#���Wm��`�O���J]~��
�D�>��s��W.����:��h�;�E�}2'�v�wZ&sN�����*��&���8AIt�~�r]]��v�_�3{r598�I+�wiS���M�[�(Aj��S�:Mg����a-]�=����B�N%X���N"+�gb4��\�����WV��L���&��DM�f{"�U^�m�A�l@����� ��x8�3So��F�R���p��Y�m�����I3��f&��Q=%�m�	�!�r�����t��&5�H�!���~���P:_�j�r�_5�b���f|_�i�}/.!T�G����J���t���+R+��0�
��R��#r:�UoH���[��v�_�X���x�J����Ka����~�4���@/\H���6�W[]<������?�e�.��v�}h�B;%P!"������{���~�A�$O9��F���d��}<P|�B37�#E�Q����g,��_�@$��\�����4�J�Y��M1#�0����!�=z3uB>m@~�!Gt�-���z�����|�b�\��$��<���!�4�r�U3"�~�n!���j���
NdI��.tMw�Dpq�+���m��eV�$*�
=�|na��=�������N�H���M~q�� �>Zf��]���M�_��(�	�F�]iX�yV$���,������{l���~��m�,�����5��J��t���60��i���_mX��5� 	�(����t��N�I�t����4H
����I[�v1��>��V#P%(`�� �EF�:8z����*3N�Vt~0��q�S��A\�R������q�����^T�R
b�@� =#�M��*�]5�b�s.J���_wo:�����[F��+�twr���:�v+X&WN����
�Q��{��I�����.K~�n��"0!�����I�Oo�U�n���)��Y���&��,4M89�r��R"�T�$�.��:#{`��(��H,��!���.�:�~�A�\��� 1��dn��j��@L�4���G�Y�(n�L�O�Gp�K~q=�����������(,�/���(9I������v���C��Ra%F�R$K3.�
�#?��Jy����a��/��Z��6^�j^�2H)%r_�I��%'��^��MwfER�H8�M��m��BEVDN�\i�U���47��d}Z�������jl����`�E�>���~�4��w$<x�-�wO�Hc��Sq���> �vY��)�k�6`U0�lN��m��,�c���[mP�-� hL�,ci�H�����@�5��-�����o�TsH��l��k��5��bH/�h��D�i���*8����p?d�i"o��I�S au�{��^��~���1���r���Mi5��a0���+��:���(�������v��<���m���]Ig����m;�_fWN]i� �k �9�s'K���IL_��v�_��	9��vC]������yu��U:u�,�a�>�����{\-Sz<'�G�8���>ftY�����5n�!�T�!��'�`���{�<q�Af��'cAX[�{������<x�,/5,YY����lb:S����7�������aj�\�e(��.%x}��[��J���8������!k~5"�c:���lS���78�J�	?�M>SH�/�28�l����"E6�!�MwfDp��'��Cd�"#�'e�6��r���=:�j)WY�����m~��&�Q��b��w�6���M�_y�6���b�-E�)�_m��xc�*8����T��N���\��=��aR�={R��MT;
�X���i��y�AK��tZOE:S����@=�N�T��T��1����Z���������WRA1�)����E�
���m����*3N4�kC@��&���b�^b�m��N�i�f
)PL�In�0�V}�b��1�WM���	wW>��������k����,�����P��]B�d9	u��3�C�BL�� ���;�?�����W:��!c	����]�+��u��dU%�����ZSfU��p��O�RW���]����h�eV�e,Y��D��:�R-d�]j��j�b�1@LQ&�N������"���CG�i����5����y,�\��@����c_,fe�����hjf<"]G2L{��U�oR�m�A~��� �g�@vt�-8n��98�9�����#T���zv���K���d��`��z_
�6{������#��u��i�����I��
�����M��"S�4=q�t�%�n�:���|V���r/8��Ys�����#�!r�oAK2��;B�0��09?2�n;E,�#K���P�����n+0�U
)%�E�.2�5�����N�� ���wzEd�U�P�P(T�(�I��6�M@�R1���C0�/v��Yk�55����8D��/pa�i��zJA:��r0�u*�l�Ty�{��&J��(�i?�>$��j9�I���Xc�@%���gK���T_���1>fgF�n���A���(�N����d����b�n�N�;Kh\\(�V���;D>)�Dis9����9�G!Eu���Y���/��M)�u��/X ����z�������u��{���K��N���zj�[�I^��^��?��b����:��X���<��S�K��G�.��k~�A�S���}�j�R���i��M�]
��6��$�h;
� 2�����& �B]:����D��(1w��������Y-�~��q4����)�	������N5�k~����PNzYL��0;s!�L�1���S�����J�O��jdb��J���z����/^��9w�.Zf@�)j6t���L1b@T(:u�y-������&�4��y��W@�_���.\��_7
~�l/�Cb e���@a�i4��3 �z@8������G�.K�:w{`U�v��a2���R�#��c��_�hM`uA\�,�����$����8_���d<
VeV��sA5�H��1�,��na��F� yN_���	�`��y���0�yq%�z��&��y���E����{}�j�$��B�
,MS�p���T9!�����]������'�}1�����8��L�-k;���{0j����Qf��6�M�y��
�K[������c����Nn1�7���QsXBi����`a"�������-a���l���E���sm;��q��X��>�la�WT$
)��>G�u�����;+��"&�3EP7M���|�������{X��!7 ��j���U-+��I�N��!1�
`nL#�Y������!K��nI[+���a'���O��aAs���l��{�h\w�u���F��O��-i^��������AWM��i-H���O�;kR����o��v���{�(��=>f(%'�.4��J��\��)����l�+ZM.6�u���|�D�����S&��c7
��lP8���������*��������(�D����Z�O�X���)�$���X�B����v���t�K'���B�iJ��W*L�W5%���f�����r[I�[�[	�]�y�k����%Jni��|�����[W@������.#9�N�����@��V(��>wk�������w(�uu���1�����C�U�.�qB���U��\��vgqJX�����vo��%\i����q���e��$����V���`��g���$�u�V$7�
� r����!}H��u�Xg0��3<^9=]Uf�����w����k�}s��Q�L,����9�P��
8V�����3��i�����{WqJ��)�iw�9G�=A����<ew$<������,Ne��1<�k�[���O�%w����M���]�R��#�#�#b�p�6�0��s�){�
��1�9�����zC%�����DN����x�.�}8�}
R5D�|a�$����a�����/���wJ���6�6��)�:O$eX���Y��Pq�P*����d[x���l;�k�*W�U/��M
�o��������8��c��7�����C���������
8�I�{��^�Kk/y���3�U������Z�����i;���gp��j:W'5���2���s8�=�F����Tp�������,���Xq��M�w����G�*�9]�G�<IG]b����+z����j}�n�+N�+�)���:����H���1$�����9g��
�aI!b@��_����c@WCPkG��)���)*�4W������n�6��X�M��&��������u��!����S�0(,Q��9!�Q��>�t���c��&	d�����y�s�6�����)(���}V��W���U��=<6��v�I�(�s�b{�����Cx�2�M�2.{V<�,fw��Q<�(�v�\{���q�"miF�eh����	w����G��\R@i����y���-����96�����Y���>Gc�!WA�_E������kM_O^7��B��C�qK����v���x���}����`�����W�@���K�%�~>Mi�0i��>W��4.�y����n������/Z��	=��40|*�N'���pb����2%z��8��hN�P����m������m����!"���#Z�Ya\f(�UE*9)I�e��x�Ym���9�p��I�DE{���q��5�WQ%K�/���d�*niN�'��xc$z�.�U<��D�)�T)t�5��x��a:~�-��\�6_����J%6n)��:tg���\������K{����`0+�r����8`��ag`'��N�\�}���������������Yj
�i�����d�m�������F���C�K��rQ���+���g�&����y�q�36���r�j�E��9z�<�
x�/��e����qx������j%W����n;� ��j�Z9G
�dsu�h��V�<�c��<�bt���r�^�o)Z����q��������H� d�I�g�V�|�j8�8S�dH��G����<�P�Bc�1��Z�A�1�J��@�g#�����?�)��_Q��&�Si��N3r��<Tzf��.��$��x���������������/��������������������^�2��:���8o�nz�T'�7�q����\��,Vk�,?-g|�V���fTU��0�M��9���7������1�r��)��eYk�U����9vsL��]Cp�.�4W8W)H/����"�����l:���'���8�U�|���z:����I-O{�
b�TD1j�E-���@���Z{�}U�R[��*�P-Cm��BS����?�v�c�Pe�^�`YB��x�e���9g�sr����t�b�Z��:����R��F�I��^��� #I�����5]]��"���$��&��<w��?�G������+H��T1�T����C��s��6��];���hYs���c��0WI=�����<2�9v��,af
��6&�V��	���j{O	N��gN�M������#K����������2Y������ 	��1������/��w�����5�d��p�p(eHL����?Iv��Orl���o|a�*�`��=�����6����&��S��r�V���;:

r 4����3������8��N�k02pJ����Z�V�o����;�X
�+���������gKeN/yz�����'TN!��N�<t�<04+)r�������,����Us���c9K��	IS� {��4k���c��o,'��n���/����_��O��*�l:���'���i;����!�r��(dsZ�k*+)0r����r)�7��X�������T|���nU��+��`�;9�&sV��96T��OJ"�����oo������R�)�4����B! ���V��Z�L.����� NB������sl[f���T�e�J����O�YS�Q�.[���PM����e�9c�r+���P��S����F�����c�su���D�������h����cd�l�!P<U�V����me2����n:�6�c�Y8��HbTT��'&�-M�9w��"C�2���z�|��	���\�9y�q��t! � ��e��# 1��T�rG�g�
���2��_����
�dNt��H6�9
�����`��Nrl4.o��=��3��B%lK��Wp�_6��5O��`�������H���O����CJ,#����p",\kgN?�7�9�q��`W��?)j�s�
����u�6��$'�(��N����e8��	�|��hgLb
c���wgZ�UE����'�dN���� XM�4af�7J/���M����B���ugAU@��==)u�M�m��{U@�r�(9R��A��NZ�������$���*e
�:)������u�����.�]�c@*��P��Oo���_B�H��D~�&��
��_c�N��u~�������;������A��C�+#��r��X�_]LD�t����c����F���.��7�s
�9vs���&T{����e���S,�{�VnN����c���}sd�H�Av�	2s6��:6���E(��&"���>�,b��`�ME'����h5@"������>��M��:6x+�qkrB�
1���~�����N�i�9��$���#������_�+�"A����olzm����W���x;��3��N�I�$c_?^6=�Qh��z7�V����C��C��4A�:[���O���j�}sL0GK�@��� ��1���6����?u��i���}J��*O�}��Oi�w��g�F�Z��������L�����3���f����KG�����<T��m$���L�*��l!�����hx5T����H��/]�UZh�������)9�n����BQBD	,{��l���ex8KJ���h��\Q8�?u9���e���jn3
�;wm�����O�:}��x;7�?�
E�7�-g�:�3C���hd�`��g�&-3�:�q�1pf���e�Q��>�;����cVv����t������~�j�]3*���t�1lO�9�v�V�-K����G�q5�A�QX0����.�N�4��:$j-z~Eb6]�~�����%�s��8���H�H$1�R�u=�����wT�������h:g �$���|^5�I�����^��Lrl�I!PR��BE�5�~��O�y����T����NW���
?�e��=2[��b:#�H����x���;�-Ts� e��D�[P����U�>8V�2�@K�U����$�nC�!2��	�����w���Wy�K�jjN����s���}s�a�ev�D����7mv��cc`��f����,�~w��U�Mt�
�l�T�T� 8ax���l�9��{�
B5g@&�d����x�����%/����.�@�Hn�������s�{���������R���]m�b��v�y�������Iz`���V�g����X,���K6�R�N�<D��=�)mA���%R������
m4�����q������
C0�v�f�s�
9�)Q;*�.='�s���m�%��v���9����7GV���$J/������1�b��������i
�.Z�<g���0[��d��`�`��e,C���=�Nb1��!���9�8v���[�:_���A,*�|g���o���P��XQ�����q��(j%�/�}C��w@���P�W�km����>��-9���]�>�����Z�����S"��9����F,�����"tdJ��yC�9�q���G$�< r��7�5�,��
9���w1�����^F�Y����[�I�����3�Q(�S��QUz����.���� XS�h�v=d����4k��H�VUl�\����+h�GOq�+H'96�����F������7�)W��.@�,�81l^�ZL�p ����&��|B�1 �
�%0��S)6^���w��*����������g�m3�kN�����A����?4�>0���sh~�S���tW�z���C�y�\�s����<%�l��f����c��-Q�����
wJ��)��Us����ObO���oO,�bY��9v$d�[�3�v��A$b�6��/}���L�}����O�����B@�@$,�R��|�w<��~��$�������/}���6��]��Z������A_@���E5�6��)�q�9��{�
B��t����a��u'�>O�����l�Iu�W�f���V�0����_~v�tFP�:+���z������4q��I*	��,�I���c���,�[��=�p�w�/Nr�6�Xi�v.!0$u�{��2/�U���N�qe�Kvt�k��g;T6������lQ�5;a��1�&Bs�2���T�M.#����������.U?UO���d��U�:���sl�VfPs��p������X���%7�1��q$���ZO�{�{��&�5X��/7vG�P����U�������k�+��T�����/��U����UL��W�H�Mg\'�J��C��ds��,��P�����Z��_��4*K����n�
�LW�u�6�Xy_�"=v������f�tu/�����;�F%����C���}w`��\�@�d���"4i���c�H�T�(���98�y�^�>yW�~Z.�C<�CD�2���ka��C����ul��ET�k/P������X]��)�I�s�f�g���O��e�+�����Hw�.Nrl�)8@y"�C~ZF���6�Ew/,9�2��1���+9)�����w��Y��7vd�`��5{Wr<y�sc`�+��������Yd�i5K���c���ABM�����/�t���]�n~���
d�e�d��6sRY������l������s���}s��VU5	����=OY��N���:6�dQ6�~{���-"x�^Tw���pSL���*�~.�SI=�(4�uw��Q� ��=�
��T���HL,��,�L�|�BbF�5+J��(!�aT��4�gt��S���
F	���$$�����:��Q������i'b�eb�����@��7����8e�g�f�{~����n(����tF��:;�P�� \4��#r@��0�������M��j����C��O�����m�1��+�z��R���kp���]53��.�/a���9����7G6�����rZUO�������j�ccX��4�N�$��X/�\��f~��N��$jg
s�?D�"�'G����M�4�:6�����,��=�q!���e3��c������.q�80x�.���N*��^��?�8e��8z@��2��/��4�2�f?��*��>_bC1=_���-�g�����<�Mg����:��{?��9k �D�3���R�W�a	j�}p�$D�V�zk������mp����3b0!Q`�Ja��e�k`q���^���`[y�b:����l��i+"8�D�i�{�����5���W����P,4?si����1��J�+��M�*�C���6�����jH&96�"�qB1��-������wdP���8Jk(��9�B�z���v��_�2(G��Z����s6�~1�k�m���KS,]CY 5���l�Ii�mU���I�}s���n:#�p�b�G�ae�AI�����I-�S�e�����TP��L�|��@�Tp���]�nC�&OA���.�%����/N���a;����5��������}`m�	B#���6���=���(EW�����k	��M�)���O^������d���j�����}��3S��P�������Hd��	\������WZ��W�#?7�%8Z�e�t!�����*�4�n�r��8�IC���GpM��]���a���j/��t����cE��gO�:I�I����1}����W���*ReE���-�wGKA3�Q�s��=��eZ��9�q��H)g�/S���%����������&HJ���'��;n�}j��
O05�d�M�������3
�`�O--(�R���������X%aP���&�N�w�o��K
Op�M%�Jqf������I���T/��:��[zy���PeVH�P�Bl�����x���`N��M�t !Rb!�QS��h�sl,�E��R����<'X|�k�N�}���)�eo��q�m1=[x��<.���M��E��������u��!���	U�r���eu����4+c�9�q�1�TZ�-{�QqW�7����c�jc��mX��.z�v��{{����=xc`3��M������#+�X&$6��fOC@>��� #�X$� ({���2���A�Q$B4>��.�D=���ZL��t���W-Nrl�ae�}���XF��W1$?�<�p�����.�|;���o�������fwI�-��D�8����u�ZQ��_���F������G��Fq���\�I�MkOuOz�iB��#F�k?�|�b:#�X�y�ta`�+r5r*�R�<F�-���5�>9F�������e�����c��Js6z����c��k�e�'�����Mm�!mO��9�q�	��������r�=�6�sl�9�&��x�M�Ca��M�y�Z�6�9��5}/��@��l!�G���	�[�3Il��A������X��xU���*P�����'�����8<��,��#�/�0J����k%�����1��������K����p�����~���8��bz.�B9�4� �Mg��2,����XYW61S��@O\�����8�����H,:�L��Q��
8V���\P�H,{�v�
/S����������M������#K�L�EI\�R5g6fM��sl��N4QF����9��e����������>�d�������J��O�5�g���C"����r��-�d�Ky�����iO<5kO��B���/-�Fa�]�g�8��5�|J����)�������"�;� �6�\���OD�~"f	(:~ve�B#9r7=����������-Q'GB]��=���q�6�S���8����%4��r��$���.{�����&I��=��q���d����W���%��9��=q��L���r����dw�kw��+r�9�Z��g���^�����6����b�T�TQ<�� z�3Rq�c��_�H�����]�J���D���]�q
m���|7](��QhH�����f�,{�
�����	�k�_.�t���e<K;�rm��G�Qf�U����jy����cjSz�M��Z2��U��i:����\=��403*uk	�N���������}t,����R��a�4,�bJ��c����el�)�����
���Z^��
�-h�����9����w������(��R����Jf�c�h,�"e�\���Pz�4��:,B�$���BX�#X,�U�Uc��7#�b.�4 B"����n�yXlg|��cYB���*����Ew���	"�N�2q������r�����<�!�!����I���������K�t�(�)my���*�Qf����se4��D�������N�{���O����CtLS����!�����C���8�lZV�;�;�{�<�M�,%�u�6�XYF&���{6#!��p�8��So� h%�����M�������3-�`�d��xV���9��I(�+��]�%8��D!��\���hnM����U���Q�e�K�����oV�R�����VG�<O(�����WIs���0���r�E�`��q"��C�����I��E6��D�����\�6Y�z����2�4��3���n�
p�~�7oB�iy:�Rj�V������a�dyH�i`��:���;���5Sbe�����&�I��w�% ���Tp����c�����Am��s�w���)��T>#�z�e����l;��gd���c���?���r]6�i��0�f�D���k���|�g���pc[��v,r:���H�w��I��&96�E%�2�7%�.,*m���Wq$?%������a��a�e������F`�G�����8Gi��q�����Z$��G���HO}�6f�w�s%(��I[�����������i9#�P�����F_�J�rs�O���#���q����������,�*�`��f����cE��"e���,')��}���j����@�a;���'m��j�k-C����G��`�\l���B.�L�-G����$�-���4y��b��u�w�����9Ch����CH�������Lh��1�jD&fe��"�|���r�;(�ij����E����o6W0H=�G� ��2$@0�J�b��*�]i��Q�k�P�6�K�,=s\p��d[���=�F>���>w��y�FA���������n��_q���R�2P�����Y�����!�\�, ,�u\�;�d�c��p������{��$W��\�3-�z��������sb{Z����q4��3Ji�����������6Qc@HI���k�e��,���N��\r�]�>�E�|)
(�E�����$�����e`3SJ=%��+�
K�%?&C�����(|�]�q�����W<���?cD���{���_< @�#R���9?��SW����-�fHz��I�a�m�U�|.�i7r�=�#q�F�n;#�H�%��rWf[a!I$rlQ�:����]��A�>9&�`��p���&o<Gh���m�1�����.1��~t�/8��.��%��M�a;���go��j�+�
{.��k��S�Vg���sl�b��Z��z�7����gi����I=l�*�*�QP��s�Ym��V��:6�U6c���s����eZ������z�(����m�t 
�$T����F�����=���g�X�k{;����]?ml��Q�|4�����nz��,O#�r�=�k��Q����t��O�L�C�+�gA3zJeBk�)tV��9�q�1T*sg��{.����n{���M%��T���"��/�zu[�iy�������v��s���o�@#�|��q��_7}9e�`�����U���}W�[�Q
C��6�a���,KW+��=��9�z5u��JLdh�\��D���X#]�'�A9�Q�^���B���O�N!�k&���x�f>!+�q��:*��vi�r����h��]��_��I���e�J:������,F������L�lz:=�(�{��,V'H�]�J�d�9��Z����y7�?���!�rm����o=~�� ���m�����|	Pr��7�����[���"��~�f��G�>�q����DK��e��xN����XI���%��C��l��>�T��C��4��v�:��:P�t�`�� �Z�������C�sl���:��2Z{�����+�X�W~^�R���Y9Zl�p$
�0���d�=��a�9�&���,y��{����������Zzf�x%��f�1>Y���H�� v�=�p)�Z���mg��J��C������c�1�FG�������}s��x��g���y��_u����c�	;���U�k$9/��/N������.��J��9�=A����4��'���+I���������cch$�H�H(]��w�6��������a=_)�b���
S���q���)�{��aM�r���]��m�K��U���y���v!���'fOV����L���?�sl��ST���\��\��6C5Z'O�N�\�)���
eC�F��������L����r%��Ul�L���d���<��:0]*%�\x#��	�z���(3g�H���c���9����vE��
8V:HX�s�����v��S�]T����t������sR���oN��k"�����u�;���=����9���4s}����yI�J��k���dS�����d
�R�2!�Jt�N�zb��l����K�]�R��y_���Od+��d~\��#9x#-��](N��8Qv|QJT��&F<�.&-���l,N�Q��sL�=Sl�V�vd��3=���M[�p��=����X�<�����KOrc��=�2����&}y���x�HU��e�ea!����� ���������`�����G4T�6����c��0����/�\:�������>���kX6���t��9G�9y����8S��B����0���#l9��I��]�����2���W��������q�n�q���5�Z���Iiw��$����E`#��&�VD�v�2���56gki��l���0^�4s0J�A�����,��W���a.)�n�9*�d��J�_��i��/M�����[�3�~l�sU4N��D���|��L�8n�\�<�g�"��
�20/����6���HN�����~}��BA����r���iN�D�u�6�X�����kB��w�|5ni�i��S"?68B`���e6��������}s�e�T�R�tCh�@i����c#p-�IN���
L������lP�����l]4�0^�:WL���G���	��Lx�ul�e���M���������k�w�TV���?���r.w������He��A���6��9��r���8`�q@�,S
D��V��x�����C�+�.����PJI�����'�P
�S���t�������V��!]>mg�s.
�
���\
2S�vD�-�;5�>8���`U"UG�
>{~��sH�=�n��V�V�E'Z=w�.��2�����^�%>�����n<�(��g���c�2�����S����l��<��fn���,�9��������-u(�QRZ�b>��A���k?�!rEj,��SO���.�0��14�aR^FXp�.��h�en���4��Y/�6���}��~�.~��y����#���������W����y?�.u��&�\�/Z>��{��LI��������9�s�)Qf�6�S�'3%��~o� �0�^����C��3�8%���j�����#�h\:�q�1�@��f�:��\������m�1���	��a��w���S�]U��omn]�|��sR���oN5���Y��z6�Z�l��4
W�rN*IEr~*]���o�|�^S�"�S������Bp�q���0q@i���������sl���R��f&�q_�������-�qG������B����K������=����H��9L�K������W��m��ri��C�AR$�����Ie�R�,�O����\�{�������u��!���X�� W�i��]�u|���D��c�"r�Tx���
m4e\��c��r%)���A�S�������]^�*��a����������}sjm�,9�T5��M!,���$�����A�tMgH�K�����Q����/���x!4�4���Je�l�(1�M^~9�8%��
w��+3�V�_E���DE6�V��a�����I�3�����3H�hT��?3���Z��3o�Lj��n�6�U*>��l�
�?���T��L��~��M���JJ?�g���%����`Y�2����''�������c�C��$�
R��������m����]���.����2�X����a<����p�k8�W(�*b���G��i}��1�2!�[
������h�_:ei��n��
�w������rA�,H"��G-�N�]q���"d�z^��&�XFV�W.�T|3jf^���:P>�y���K=c�J�����ccq-��oe7b��Xf�����Z���M���@�qSo\�/��?$�Pk��2�n|6�b[��[�*w�AF���C�Cl��>�`�p�������^��c�*���g�fs&��9vp�<���������z���/C�/N��L��A��[~�9��Y�7�V��p+��4�6�K����Y���ccp$s	������1�.������5�x/E=�d�����j[�+P��>��A(�����������W)U$�7Z�������B���O��LR���#�L�CG&�5�~dLN9�����KxWu�"�;�3�v\�0���b��H�$�������j��=��s	L�14n��c<#�X�0y�|c`UbR(���T:*���Ts���ce>+1(9a��O6�ki����c�0�H�l�����8c�c�F�7/Y��9����7�:V�\���I�Y������96X2O�=#F���RH�e��I����V:H	���2h���U��RU���yv�9��c�hM�T�L������I�+�]`]~^�"����E?��p$H�2��r%��D���w�=����ob����w����N�B��0�f`:� h�_��qh���O��:�����~���@N��Q���xF�����g-�����@q,���uh��Ik�}t�B��`q0y��7m>�v���6�Y��,�,R�R]���$L�W���r�-��b?����l��cKG���9 ����B��<��8 ��M��=�i
�������e�5��|�.F>F��8U(��q��q��#���H)� �@��6=
����*kY���+j�{M���B�/�9��T�S'Gu�x����#V��l���$he^d����s'z���1f������_��<������������?��;yn
�R4�~��{���;��\�;����\�N*���b���\���H����!5��>.���Ue_�C*w�eKs
��c�+���^'
��s*/��8vp���f�N)�����K>u���)��z�/�k������s���~s��XUD���H��FZ���sl�F)�@�g#�nv�P��|�E�I�����������f���	0����k���$��\�z-������|a�w�_}�q��\��w�B��#75'Cg�������9��=������T�%1t������H��"���"'���g�����*��������f�Gf~(������]�\~����s����=��v�:�q����*�������S��9�����|�l���%��s+/z�/�����I\�a7�����#G�LY<��_{��9��y��ul�9�+1%�d=�pr�)���;*o����H,�! ���}�u+��Qj��
"�L5"�������q���}aN��kM����9��/�t��@G�������S���:6��%��Y�Y�08\`M��V�W+S{��ZW��{�>�v��������~7�����<d�008+i2)���R������V�8�&��H��R��^��u�+#�~�|���B�f���9��i�7gZ`�	�Zi��"(g�!�ul������$��,7Zf:��d�"5U��gR��p�G���������/��"h�ul�RuB��j�|�~�_���2����b�P�1 (��$���3v'>Mrl��v�Pp �!�/�u���Je^F���Q\���lz���7P?	]3�Fv�+��'��f���+U1L�*���N]������5�>:������>B&
��u�6�XvOL����D�Q\f�������!��ZLI�:����l�b.��02{�bL�����bY��z��m�|li��u@�����z3��1�JF����UF6�7w��A$2c�1�d�gv1$�]��betv�u0�Y�O��o3��Gn�������B�v����=�F����LP���d8����'����0#�)�8[ �'8���,t�h��wa��.LUNzb�v�,�)l��g�RQ���`Ys���c�����i�e��[`R���c��r(/� �\2�v�"�3g��������q�;v������P�e��'l)L_'/;e���c�P�pp�����2����
N�q���t!���t!V�]����|!��|�R-[m�
��dY�R�e�*Ne�	���l�P�#wu���f�����w��n$e�#9K��������H�����K�MD��MO�xJ��K6=�]���g3>����B�Y!*��������mJ��9�q�1J�s% ����/��9~��03��)Wl55i����\i�*_������k���s�a{F����Pd4f,	LW���qB��b�1JHJd][����`�M'�H������]�WK��r�Zm�sd�{�
"�Sr������4��U��*N��
����L�~��U?s�D�7 �&���ul�%��l�jkukm8{�xW��&�,-;�Tk��������o����z1���d�~zS�*�#��R�@��2*`%����+/��F��8�X���I"d�z�-"�4��s�6��������\�P�83������"sZ�#k��L��(�P�6����������=���R��V���	���Eu'�<�D�:��S<r��#Z2��=K���&96�SJ�����
:Y���z��0�����)��(AGj�\M��B����Fs�){��D�"N�'r��T�.q�/�������$�����%3�1?Ob�f�S2��0Hg,�d������<g����������>�i
�V���#����C5�>���3�&C�����5��nc�10��XJ����v��iqb�e��������c��)~sde�I-c�R�z���h/���b.'I1u�K�AfOo�>X|O��Bm��W$f�����(.�I������l{��!���c��Z�V��d�ti�&o��II����3���S�/t�������{�
B�4������������������s�����BL)���1A��L6=����f�b:#�X��xh��{�S�z%,1G�������)��8��ge����0�F�U+�9����
:�n� A�c{�w�����.FI���l:����l���k>���$���
�:�*�sl���QH8vq�9e�e�<,���$T[�9�����J0�+���s���D�I~
�T��9�X�9�s�g�fO�S�(Zs�M
p$d��Y�eP��2��� �DB�s��l~mz�w�m�|�R�H�bz��Shw[LO�W��;���*^�W��$��)On�C�q��Q��m>������!��1�4�,_=Y�g�W���6���b��NIY���Vlo�.�Z��D��St�����G�@1�-RT��]X������by�
�\s�z�s�2�����(�H����G�����"=;�2���"A"�F\��l�d�_u�O�#?7������+_�Y�8X���p���c�����A�F��3�b�������"]e�S�8�bz��D�y+r1=�����6i�i�I����g<�O�����u�Um�����!�\�������z�]7����9vp6@��(t z�������"?����du1�sN�s�o��+D2II"�G�8�!�{����5�u]@ ��]h�������Y�M�j
�N����O�5�$��*��=����,��������]t2Gs2)��P�^�*���rU�9���c�@S*�
�s�,#���v����4�}�O��%1\y�����g�+��U1�AN�*�"���oR��,G,Q���G�I����}r��k��YN]�r�'m��u�6�X���#���O~���<�O����so�k�t�1lN��;�p��L�)�u9=:`�M��Nv�bN1���2�;e�{�����{�N��xU$�������9��5��m��}����*%"M���A���@��
�LsY�A}[Li��V~W'N+������|������l�I.�ZcW1=�)������Mg�����<��20�U@#)�����qE`wr�A�>�9��L�r���]
O��I��]�nn��e�uB��]����X�S�]Tt�����o���9��9�7gZ@��P-y����3�)Xi���]���K�X��e��,�L��4m��PmT��M�����)��)C2Tmg�$�F���$����@�S\V,�.�������cl���M�v ` @b"��y����g��
���B�v�)<���Q���������K���6����w��u1��k�bz6���8��n:#�`�byHue`�,#�J.�-�I:F0n8'g�9�q�1F([�-4�M
��S�v�
8V:�5\�5��������>u�{JOr4������c���}sd�I]�C�{��Y<��c�X$�����0�[;�>X|G�I��4�;����DMG�����I�k�yF�OY����(K����w�����byt����'�m�W��B��#���gc��}��Vd��A�cY���I :YxC�_b�����Hj}
�d�>M�����d�O��g�"Z�����e�?;��"�P���t(4��}���>�����;=��c��@�|��J��E���up��RL���"�'<��9�r
,����k���!�M�������3M����/`L={�������96V��F��5#V6^&e[�3�A���>�����G��a��oV�����s���:6�T.���j�C��S-O
�������'2��.(S�v�}
0#U�#�H�@
���xOo\�{[
&96#(�abV�ZGWUF�L'|;(:�v
�\�
��������q���������|�>^�[��Mg�;�3C��
�%CJ.��z���;x�����S���B���m�Y���c�!�XC�MH�����-sA-�
���^��2:G-���}`m� 	�@"<z^�A�4����k�D��(������~���~��V�y7��
4�~�	(������������5�C�u����CW'�fo8���2�Kb��|�)��,|�8��Q�5��)������R�8S�h�X���E�������i�c.M|�I:��S�lz� ��~�bz:��r������T�{(�mdv�3���*v<�o<�(���q����qB��P��Gv�U�Lr�6��\Cr���N����l��%4�M�����^����L:�o�4�c5e�j�W�]�Z�y^
U�*y~$��z�
.�P^T���S6]�8�,�'S
H�kC��nW�-�y��sl��0�t�g"�,44�����z�hfPL�t ��s���I����r&96���A	�J��~1�k�m���K�+�
l�7�Y�F]�j'�i
3�G�D@���l:#�h�^yh�mdn�I��P�H�����*�-k�}r����&RG��Ep�����_��>��X��|D���M/�����3m�����c���}sde�&d&����g���N����)�y��H���,I�C-����,i����L���!���<b������<9�96��PeN]�������W�#?7�����~!���#@��8���#_4�5�e��1���)�!��>Fw[�]si��^�
���ic��"�2����-6)���M@��\6�D��%�?=��!�������<�F������Y�Vs���c"DARF��J��4Ol���;����ceRZ8k��R�����L
vY�������t�9m���9�q�������}*�=]{�$���JE�Fdj=k��r2z�^Tp���R)��@���b�����C�;����c����T1�<��������o�
�/��, 81�M����d��b���fwH� =6yu��p pyj�	v.��+��!+���+�V.M�th:�#B���by��D�ysa1=�b���w�A���?$�>4��
�#z$�9��9�M���C�QDbBr��*��1�-��m�9vp,Wm^6	{_*H����}��wU�D4oc,�s�a{J����XT�5f���{�M��e{��a��Q����'x�BS��fP^�Dj�.�!� Q��E�����d����|���\��Q�x/���it������;���9c'A_@������ho>Go��� ��"�aXxm�s��M�-=�F�	��O�u�
�����������"�����6�w��3~zQ�F
]�
Jl��rqM];v5'ZS"f���C�a�2
 IT����O���:����c���
GN]�8��,lz�z���,2Z#K1�sN���o���B�AajX�p=2��xNK��cc`��
�,G �������e�zI�I�5��w����G��"�K�X�g�Xli���� T5�O]������	����]�q�I���K�>�f��b@�..����z���gu��96H3���v����
%h]>�->��Yz&����qv�������d�y ���a�j;��O�-'��,U)�xvc`:$�D(��0R�~3�e���c���A�	�O\����&9vp6D�\��tN������)�=z�f�7���c�^t~sd�ATS���uds��j(�8�����v�#Pd�e���	��Q�y�[1]�x����#�Kmb���;�g�c�H�]��s=�)�6J�h2�*~���'��������_	9g�}��6������ �SY�eA�u��@��~��\{���smZ���g*��>���\�	m�k�����"��4�����,P'FB�U��T����R���I���}s�s���9W��="
���)���W��I�21uU�`�#n������6i��i9��������#PE1R��-�+�����*�ZxY�\Yw� ����>P���$�/��V�f����G�
���R�C�Tg�4Wr��A��\5R�0�:,\�������
�_�Gj,�HHID�L���fT�� '96r����h@����/���2���c�6��!�����8#����1�^E���~��K�b�y��vF��:����������D��I?��L��V�8���D���8jO�7n>�R�s�6�X���C�X��fa�
��#.N��j�B����N�n;� ��h��8�FK������u�����L��1��F��,�k�BN��e��w�d|����X,��`QS:�EUVS��T��~��I�{�
bQ���!H��fL��2/���'�8��T~lTJ�<Z����B���0K�p�Q���0eX��c���#���:�o�A*e����^6��N9�Oi��zi������W�~fw�s�4�sMm�w�����q���tF������9.�wGrs�\k��q�j�#��h�����8�lDN�����,���p�c�!�,��B�����+?�U������z�/�[�}����n;��������q�bbN�}��0m��8D�p�2��3f���z����3p�k�vS���O�����P-���D]MJ�;`}�_cHeR&)��p���,�[�_�WKs�����-��M
2R":8�q�X����������{�����Y<
e-�2����4�t��9��J���9�/��~�\�7r@���s,����n;#�p�e�G��z�jV�/���g���fs:�k�}p���S2
-b����?�$��s�6����; !@��Fy�e���Y�W���G����5��9�=-����8�+81w�['�������Cr�a-��=�	o�#Gx4�����u���v!,����$	)�����7��c�I���rA���I�,���W�$?��i�by7]�1}w��gC ���~�j�����96}�d`$@�]�����/Z%��I���HO}��E�11����AQnEu7=��M��I�a;#�H�������1sb���0�k���"������}r�<'K�����=j
�YB�=�n���NaI�?��v
��#��2%�G~�a�����r��sR����T����������r]4's�sl�BIPM�r^�_A�w��`�M�u#��v!��8X���D�!zZ��f���sl�,L9Z-���B�����B������.�@v��a�A=���s�V��	%���T�$�<
��\*��P�q������}=��46*>l���xM���k��'S-Rz�3������u������AX���+�\��kG��\�y#�9�q�1��@
��Mni�"q����[�i�G-��5�6��9�[�����
QhCi�����9��I�7�V����b�����;W01��n��14r
�E�A�yRj�{K!
n�=�����G���]I� :�l���/F=E�����w�bIt���B�����jq�4�I���G�pE��{7�#B�sl����Tt�="4�������i�4=��V(�
���a{�%6����|����I!Z	�b;#�X� ywa`�U�)W�����6���8�A*$�cB���L�:����_�i�|���I=�&�J<���*Q8jc��tf�9'���oNu����1W6�Hzf�������\�J�R�����e���~�^S�B[u��W��JP�t����!��kd���"k��1��a*e�X���w�p�e�im��W���.�@��|S���i��l��;���(�`��{v�*t��7��Q-��D�7;[j��}7>�#n����mO�["��F���/^�[������)<,��f�ezF?�$c����c�sI��]�x�9��=�n��
��l�|D�����_��o�O�W��Wp�_A�$��4�M������#K�H	-�rA�7�mV����	YsvV&���"��V����P��g����������\�Ct�Z��|�����I��!��?G����LB�U�*H��������9/l~����_���<�I��m7����96~1U��z��
��]�:��:��H:�{,�Sm��?�������2��c����n]�n������-Q�G�]&�D�1�0Y�29�l2g$A���c��yN�B�����a9>����c�qR@����kA��b�}j��
Rx�Fr�n;��������#p��_bQ�b��i��k.��l��d5	h�n[��{i��J�Zg�m����:���"S������'96�U464��i��a���X~^�B��A�+�Rl
q$�<X�������A`���=����$DS�\W���@l����b������sm��C������6��?�'KR���j����������m�5��??��v�O�S�r����FJ%���J�4Ei2�����q���c����o� ���>>����'9vp6���A����8�-�i]�ry�Cx�
��l�v�AlOL�9�p$�8Y�b+��=af���]�����e�"�w�,B��2�[�Rd�����X,�a�`1_���*hF�w\!4i��c�XL�)|+�����r�
�*���d)e�O+Sr�]�t��v��Q�����)t��cc�����Z��q��v�I�LI;Urm��gRJ>O�����'Y���\q
�2<��=l�eK����yT�A�l�C�K3�R��wbrv��Q�i�)�
��}�L��`�,�q1�M����zv��d�n��]� ��rV��i��Du�������i:���gq��i9�V&�i�k��:��4��`��1�BvK�&r�6���,�-^�M�1��V��i!/�!U!�t�p2L�62���)O���
"RNxs2�z���a��a��^��T��|��KX���c�?����Ba��	@V1�%P���&�t��z6&R����A��Q"����o�E/-�C��L�����K�;t+a[h������tK��u��'�jH7��+�"^�����N=T�40!�L!�q�1��k�}p����T�q.<{��2'o���]�nC�y�L��]m�^B�*��4�������F�������8~slc���S�d�3�a�9D��ccx��]
F��k��]��������j�|��FN�h�`/���KX���>�:6�FgN�����������[�u��5gKhR��^;_����B0�q��2��<G�����B�I�
�<,&"��Z��0]������i<���Cz�y�-q��������-�zm��a{2#���Z�u��xFp�*'��1L�R����,�Q�p�������J	��P�*����6������m����
X�7�=����Un��)��C��N����9����7������P@>�E(�S�����d��1�r�j�F�L�j�=m�������������0^�<�5��J��N������ni��v��A�b���	M��4\������j�H����-=��r 9[bSU��e�������*c
��zL�];��-7�u�j��^�s����+/�����s?b�����>��Y���7n��c<#�p�uy�y`4��D��:d(����a'j�}r�L���s�Dm�aEb#s����
8�����#���Gbc��e� �T���Y�qvU1�s���o���cQ#�R���up���0��1,J��<�	��6�_h��[�P0�X�^~���x!4�4���%���Uw��&�5F�_�;	b�Yl�����'���R����>o��B@?1G��H;�����0��A��r��������������_��5�$'���cI����S�6��5�O��n{6Q"[4�g�3��Tiy(we`.%N,�����S�x������!���e!��`�u�0B��>����c��kNr�����8g��\��$?�����A���������}s�a��^�7s��S����t��96X���nV������#Q\����>1��J���Bp�pe��,_G,=������>��+��2�59S�i��qh��������ws�N�n�P�q�2d(99�f�>�9����96(4<yD�I��M�7;��&��No�4�����<�������*���f[�~��M���)��R�3������Wf_���X������M4:�q��2/�:�91�����Lr�6��F$LT�J=m���q��������cmd���9��)�7�V����S�a��k	;Z����������.*
?��������*��~��w�������D�Ome�O�)u�9���c�h,/����iM�P}�^fr�_E���L�7��*���o����9�������Y]�{���?g���%�Rc���%|u�
6�T�W��o�)lC�T�_x����B��-j���9����l�$J�Nc��0�^���<��20Y6$I�N$I{�s���c�+bE7�rX���f-J�s�6�X�-k��H��o�&[����>��U�*$[Rj���s�j{�����EJ���zu��F5���8��1����!X������t��p�|z�Y�����p���5EJ�������y��sl����0�U
�5�?C�P��_����Pei�z/�H`F���D��M��&��96D5L�n��8 �.3��E�A�
B�&^:T ��S�k;��X��>�C�OR��}2��L�m�5��??��xF��*������J�(����g~.��D��c�3�\��
(w��;�u�c����EF�*���L[,�.N���y�������a<�(��i�[:��\���`�������96�G��I������Zio������H����*/�F>�FJdf� !����h��&96�F��(�Y9�M�H��*����*���R&���_���-B��{�*:��u��A�1H�gh��+�
�)u�fnm��6e�Q�3�44������j��b�:��a|6e"�Gc�s��d�N�<d�:0�*����-�;�~��`j�}s���$��=#c6��M���m�1�8%
�$�k�����S�]U��eG���v�ImO��9�v����De`tmD�s��y��ccpEKD�D�z���_�\��V!-������x!����`P>�����,m��_�hE����8`��y�V�|�����o�y�
�&��Oh�l�P�#A�U��<������_�1 BHT8�$�������)|J;�rm��g�J2(Okmm8��>Y�R�	p���������F��5�a<���J��C�kC�R"p��
=��FS����}��A�"�P�YV_fu���m���d����z��k�25f��aq��U/�"[��[�nw�9G�9W�����P�&)9'�`"������s��p��[�j��� ��	���1m���<��#����;��?{o�+Ind	����(��q�=O��w�/�����`P�.��G�T=���KFx�����$����*u�Z��r��1��Elj���8����g}pE�����9-��G����GR?�75-�����	YX���x5�(,��� -�k4���0d8��g�a1���K)sni"	�N���p����?��~��_���������~�?~��_��_����������?������_��O����]�O�O���K~������o���������MN��?������!\������?�����__������O�.������+�?��������^��O��E�O��wX���{�I���u�{� \
�\��W����s�&_)����Rs�������+�&xf������+�ftf����Q��QL�R�f|f����Q��QL�R�frf����Q��Q��RkF�gF1�J��Q��Q��RkF�gF1�J��Q��Q��RkFagF1�J�����+�kF�gF1�J�����+��"���+�f��(f_�5��p��/����TL�Vz[�3��}���3O���kuSh������[�"�������S�9�Z�r�S�9�Z���gn1�Z�r�S�9�Z�r�S�9�Z���p
6�_�[nqJ6�_��fN���ku�-N���ku�-N���kuSn�)���n��)��~�n�M8�������gn1�Z�r�S�9�Z�4�p�8�_�[nq�8�_���N!��ku�-N)��ku��uj9g_+�i9��r��V��X��s���i9��r��V|[�3��~�n�����~��u�<�����-�8�����M����s������s������s���i9��r��V����rN�V7-'�Z�����[�Z�����[�Z�������S�9�Z�r�S�9�Z���xj9�_�[nqj9�_�[nqj9�_���O-��ku�-N-��ku�r�����n���������N-��kE7-'�Z���Joku����M�I��s����9=�����-�8�����M�I��s������s�����3��~�n������n�����~�nZN:�����-�8�����M�I��s������s������s���i9��r��V����rN�V7-'�Z�����[�Z�����[�Z�������S�9�Z�r�S�9�Z���tj9�_�[nqj9�_�5��S�9�Z�M����s����Z����ku�r�������Vgn1�Z�r�S�9�Z���|j9�_�[nqj9�_����O-��ku�-N-��ku�-N-��ku�r�����n�����~�nZN>�����-�8�����-�8�����M����s������s���i9��r��V����r��V����rN�V7-'�Z�����[�Z�������S�9�Z�r�S�9�Z����Z���JnZN9���������-f_���SN-��k���:s�����[�Z�������S�9�Z�r�S�9�Z���rj9�_�[nqj9�_�[nqj9�_���SN-��ku�-N-��ku�r�����n������n�����~�nZN9�����-�8�����M�)��s������s������s���i9��r��V����rN�V7-��Z�����[�Z���j�-��r��Vz�r�������Vgn1�Z���zj9�_+����[L�V����rN�V7-��Z�����[�Z�������S�9�Z�r�S�9�Z�r�S�9�Z���zj9�_�[nqj9�_���SO-��ku�-N-��ku�-N-��ku�r�����n�����~�nZN=���_�O�����������?����������?\����S��_����������3����?�C���������?�������?�_~��O���_�������������i���?�����������_���
W�m�?7������_~�o��������~���O�Bh�q�-��GB������������/6M���}�����������W���
����������G@������������m�����v���Z���;~��O�����[���u����?���?��w���-���_�F�������<�K`�O���������/����f��������i������5�G���_���%�i��4�g�����R�|���c%��: ^�����[x���������O�k��mW�o��������C�),�>+����D"V~\6�yM?9��?�d�����g�V��K�=��+���O������?��Y���������_�~������R�������Ku�(h��G����w��E��:���w|zB�����=��ok����Hb!FU�X��1.�������
��22�M
��.,g��c�[4�X��9&�^���+;����nd��EuAe�dq�g��Y%D
��
�.�|��*��=G����
���H�+��)�)
�D	W[Q����pe�>�#3�&\�4��� ���7X6����pDF��������E����0<� �n���sDCd��t��c+��EcP@mI1}A{>�>=Gq4@�|@5��������jB@|^+# �aPF��r17���%jG0��PQJZ�	]����u���)Vf��B
�BN��p�����A4����BS���\�[�O.��}�Bu��M����D��[��R�5b����xp\�>&��X<�����}�p�i���i��!0�[=��m,�#��� S�T�`l<��@�>-��r0	�p�m����%�W�������N��6&d�ibB��	��{Av
M�k��{0���C9x`�(�X"��;��(�ZN�nL-��P8���);Z��(�Q4�N�D�n$�A�K4
�K�N�u��AS��Zp��Z�>�����n�rB�P]���h
[�O����[�[=88b�rE^��e��T�/+Q(]���s����������?��[��!4��x�|���>����T��Dj���/~2:���������w�C���(��
���c��D)2�[�(�]~��������Ne���?��~�G���ZVu��K�,^~0�����s 'm�j���pkF�����]��*���*��/����%���hQ0w��	C2�X�R����[G��cD�%
O)N�1q���{�[)N�"�/��^z����������F����Yd��or.�q����1*`�'a��*�($2fDH��C�@��d�p����`Fcht�I��fJ�A���/���B�G���GA��P�f�1�%n���@�����E�o<���x�/T�@��?���z�����4`��-��Z�1nuf�(
bJ�-�7YxCL;#���K���|�4��oK�G�I��I�
y��F�X���
�R�,��P��*
N���@��&�i��<",4��L���n9Gh�N��!N:������V��@~�e!�1Ix�K������d9f�V'��������PA�-�^���7��>��X�:!Uf�"����`�y`��Z����$�NS;`�dD�5�y<fL����t���(��<���	�:
L(e-���{~�?�sH#��?��h��2wkx��������nu���"�|7cm����|F���������%�&$@����jz��$Z��%YM��t�VC�MG|��\��vtd�A������q2�y�Sv�}�[��E�*4
A��!2��o����Vae�%B���,rR)u��{�XzI�1b���3o��$��]����rBb
����wQ(?���B3#$�Y�����4%��L��0H�O���<(�g�1�@��!/u�(��6��p��,t�D�P���p��o?
�uR�������<���O���!HlK���A/���=9���M�t�i��<�-���R&�@}/hh�=@N���dz8����l:�����\�����M�����Zj������$7�N��Kb�l7���[�[Q,g�bL�����6�	=O���r�X-'�c�$
I�'���aj�1w�p��^Lm�17Un���U"������������$�W�����1\����Z��On�$��&s��N���)���I���M�d�
ig���b��{�a��|L|�{�T1�-�\r�x�1wD��6K"x�&�/������p�f\��Sa�z�~�����B��t+�MY�Z�*���<*�@tSO�&�#l�by��.a_Rr�}�[&��K�^�c)�EG���t���-GP���"�7\�;��j�a��e9����7���&�O��q���C�������cJ�
�:qh�R�����i�	�6MK��i����+��W�y@�� L��FQ
��JZ*gF��t���L j(�
��Bx������9\�bV=�"��o�o ��J�T3�
cH�
���	E(6M���4W�g���M���\M�����S��������N��C>��d�UL=�L���8�&R����W�;��ZE`f,A�.	�c�Ao����X��%��Z:�.<������/l�����t�.�O��;Zv5�pDJ	���xP����[}@�|�N��-����
P_T��%'���k�������M��'j�]�j;O&�y�o��7�n1iC�6u��
�����X��
A��"fF���hcM�6�^��f\M�V��Tj3��G�)_ �"�rSG[&N����c#jJ(��_C�d���nQV4F
c���=���0�%�p���-5��#�7��Q[dM��t��n�K�1	��HC
V�����l�JIyX���]�f��1n�aPLEL�b)���AY`�+�����P�T�^M�A!�'�;������9����o�����))
�����wQ����E�|a�����=�=�A�����#����T�Q�S<�-O���&�=;�>��h�����K���GkI,��>���X�R+����������WI-w�d"��srn�4E�9Ys�[�;�B���)��M~�-�E�P�n�����2����MeW|���E�$YW8��H�h��O����v���)�Z����
l�[}(eE�Tt�6��7G�?L_RG����-���< �~�RH�A�Tp���H�z�o��RKY{4�[���<[���*���b����1��1�����W���ch��5��p����	.B4������4����v%I\"��Y~�qW���.J�*����lJ�e�<e�"�r��rG�%T3	�1"�����L����� RN�$f%qK'Z����6�z�q�(�hY�cSG�d���J{NIu~������v�=(�(&EC�H���G��B0�L��l�eHB!L��`j��qt�-���<(�(��Q�)��yk`,
��Q��(&�Cnc�6�k����z2���$��Z�|r�9��?�=�jKcA���C>�C^-�3u�(-����G?�M�������,�DV*����]m�U������.��X���������#>�K�\�����<:\�����:<%�C�e���=n	�":�<���d��h�[o=ny �cP+����h|r����)w��
)6D@�p���~�0%e��A[��%�R�n��	����lH5�&,`�����$�R���%W�y`��`�s�+ 
����)��i:E��(��`:
��C�(_.)���tP}�6����
� e��;���e����7���?S�(��O����y�)��M�sj_��6���������Y������W�a���R*���^�������X�T�\��w�\��YZ�
)[�1wN%�����r@`v��77�E����l�����+!b���4�FOJ�v!t��W�R�v5�	������8L%qnGZ8��3��NZd��������f�_���G6W�yP�{P��,����4�1nu�P�5���H-%�.����/@�|��D���y5���{N��Q��E���1�k6���}���$�`�@+��/1vxbu���b���n�=Z]bR9�jz�����|��#2���w���\�����(�*E,�H�<��������E�������A� 0��+��x9�����S�Q� 6xe��4��������6��J��cviu�����;�*)�I;:�����������j�` �S��2d<Ntq55W��0�jai6�������Bc�T\�<�I0r���V�yJ1����������t�!�q0�g| 1-�2���r���t�;"�� =O��B�r���VW�pHUD@����%�vXx�����K=�r,�RW!�c{n?����b���`�NnXK� "������\MG;(�AzUkG'�������!��"cX��[�;�J�eL������0�u�C�z�qK(��#���e��D���������R9>�j:f����
K�8��;��FSj	/,y���V���<9��`	N��{j��a(T���;���B�Ga�=ZC:5Z5�q����[}(���H��
�����F%���������i]^�L����Hv�$Uh������������o�T9�����~���M<L%	��lGC�S�T�B�jz��E��������v�Z��a;h�55���@X;�5�3GI���f-!�S)3$R��z��*J�� -OAQ��{�[o=n��JgZ���T��\�sROg�:�N=�\�X�b9f���i��l�0u)@'mjm��i���VL#z��Y	J�u��|��/)|�%��*Q�M���w����C@���I�BaPc�-��`�iXV��|�M�_���;c9��l�l���j:%�������Ni��t��:��v��N����� A��,`����o�U[�������Q8iK���~�C���y�dAZeS�lz4�bR)���)T�S�*��^MD������L������0y�[�;�T���j��W��9yt�Wo]�e,H�J�`�2��l��z�2�Y�#V+�������yXy��J���L�PjBU���nu�0��^zPZ�!���������t�FL�\�����0��Q��(t��m�:J�����;
(6��z����|x��,�=��>MD�����
U��S7�9�s<��>J��/3b���leg��f@jx�\P��O��+(�%���b����x��5R���\�����)%yJ��!ZliT�aP[��[�;�
�HyJ#k�=���n*k���������
Z��TUOsLN�|�]5���1%������ZyG��w��-/�usC�������T!��W��9�xj���T���j:J��.���q,-W
���zj��.�r:C1
��tAnR=�����p(_�(��]j�d�y�o;��Y��N�T���t���n��wLG��{@��8������QJm���fTD.j�#����H?��������
���6�J�#��9����:�/F ��z(+��n��5%��w����rDR%h�N�� <�������)���I-��MsL��<�B�RuQ���1[�>+o���r�Y�A"���(�1��
��P��� �Ko�AN��4����a0o�����tz���HL�$1w7o85x��d��>:8�H����T�Ms��C"_%*I9]����g�y`��$����`K
,�x�
��`�A$�l�)E���|�ozYS����,��#�����.^�*����X&$��dj��#B�����o��R�h)�g�sE��qiPr�}�[(�aIT�����v�2����HU���g��[����/S�������.c������[yG��*�S�
x�Z^
��c2�
���j&�NK����$0��^�/)0I���0�3����T���_���5�c��>��N���G�����2����N�r}���-u�������
L�����I���%^u���Rx��97����W53����N�{���UZ�,����o�L���rB�j{,��)�@�O�j:"�X�V�
r��R`��1�B�HW������� JH1��� ���Y����^��x����^
[N7��+��)��Sa���Z������MX���7����h)�������`Z?Y-�&����m���<8|F����������<(�=(d	���������G����BA��$��S
e�n�?)�U����5i�X����d"� ��\7�������4��b�$\�	ue��k_��6�R����������`5>Vb�����������\�R�������.^�B�U�;�"e�&��<@&�t{��~�;�z��(_��)C�������o�����Q �_1j��.�xJL:��aw�)P���Bm��O������=P��<`�����c��n��U�fy�H��-^�a������/�B��������<@�=@M�U*����Z�0����[�@Ud6A-�A	�[
�g,�^�R�r�	�" ����<�vD���2`���G*"}TSwH��$$h��������F�F���aym����	[������Xn��~d:�H��W�cI�%��|���b:"��"�r���P!8!���$����
�e�N��w��"��	7����/>����4��09����� .����,��ibicU�,]m�������ceC����h}����mp��%��ThL��MM�<�Tytg�y@��@�z��ND-���"��@��S���9��3�����lLK�|�8[���62d�yb���
��YFF����B��
Y�&�*� �I��������V���:9�;�;�_{�q�����Uv�]m���P����/��e���rx�j;���
yo�*,���L%:a���Tb���<]tH��[�;�"��Ft�O��z"lUZc�z�q�]�����tasW/��Ls@M��|�=<�B+U�W�1��>1.���
U6Q6�����uO������R����p�,#\`�*sj����2�D�|9��N��������������>C�Y
���P5�	��@���6�B�x_~*f(f�k���D1v���HB��������z��3��(`��m��~x�f���yiv�E-�K:$+�_mV���*���`~%���6�^mGD(�+�2��{� ���#�G����'IE��w9�����y7��6���T<���.�(yY"Fcm����2�������/�r&�j;f#�'f�lZ�����9
��`
����1S�7�Dc���
1���9$q�/�H�E3K����v",�,�2)F�T��q
�n:��E��_M9A�E;���O^�*�*eL>V*_�^M'����~��!KV�4����I�*�t���4fYnh��`��~�\���/���L�\�}�j{��Dm�������f����V���������z
���Rjv~7��/����.�B�M��!����A=[6{�[��b�n&��2h[��<���P .P;�u��S��Ov���k�~��a[�t~>��WtF�i����	��Os�55������i��t"�z?T(Qt����K0hD��c�'��<A9J�c���]<�Xc����J�%��'���DQ ���"��N���}.8�d�r�3
���T3����g�O�FU�^�iiD�����������������\K���,�V����\\���������
�D ������N�{��X�JI��� -�d�p�co]��J�Y�#������t��yG��Z��`����v�F�O�>��������8���������Dc�H��
4aj�pZ�WA�SjQd������DX�XT�`�_C`h����������a��=�(��M�-�p�',�^�,�2-�eX�(�j;�i��4���[&,��O�i�%
��H[�\�,�����:���iyi��e���K�>��s�=V�S�J���.�s$�T�z^mG�.s$�x:�@��*��e5ni
)'3K���r,���i;..�6Cr�*y���.���5��Mn=O&89G�ew��^�Z�l;f���n��j�W�@�/���/�����X'\A���*�{J�5)�D�d<W����;�J���ty`j&D	�������������:zk�����,_/I�%���E���DQ�vE���	8������l��0Um(1��h�6�������-x�R���,X�h�!�_���HH���~���`��2A���x�b�L���^���DBn��
�;�\�E�dP%���9�/��
9�&�`���-�����0XR��H�e
Tn�<
?9����pZR�R�r �����	�'[6��"0x��7��D�l���X1�L�ecJ��0Q���9�G!��,�e�xw�m��"�]��SD�A�Db0J���X���C���o���~newR&?�0�l�/�k]d���{�����&naR�5�)�r���2�,��j�����h���z�S�z�����A=A�$�����������;������`���2��#�l;"�h�)�U������s�dE���;K���s,��T�����S!,2F����[�g�NY��������]��Ko^�j[�^m����$��]M{��A������4�Y�P�[�u�5�o&�Q)%t-�i������D��QA�z���Dh�}h%$rI�P~5���1���<�Ek������
���yA�E�|�F��P���`��(��0S
)���K|x�-�a�(����
�t�qs6��a�qzs����&_Z����\������2����
���`�%7��{�t5`�L��"_�i	��k�Q���[nv�cC"g���}�L?xOOJ����T���[�g���I�0zK[��&��S�=�FE�RI�^m�l����Mk{�H(`�Rs"m���/�
G��tJ��sb����������|%eq�J��j;}�I<2fj��Q���<���LF*���9g��m�U:�|�Xw���N����B!�YD,!������$�<�C~-/�O��u*
�qfK���lI=]��|IKH��^:J>&7�c�*��BV�:������|4��Z���0�E��V�/�t�
)DC ��[ja�|��g�;=��9�T��i���V7�A��u��5*B%�An��
��
��&_u�)�b��x��S�s�Ov5��#���Dh���`��N�:��'�����}�<7�S�*_��|�yv5�����A��Oh��`�=`%�<�#*{��{n����z����23_.h��X�����E
�)��=X�O���&��{t���"J�raRL���Uo���0c�h�hZX��y�����'B��)r��F�q3>�s��X`W�cy#���y��f��b�7Z����q�Y�tGN�=�RG�{��y��N��w9F�)��f8�f��}�����P�r���1&TH����h�a��v����A�SjN)�V������X�<~�ie#����0sy��h4�4r~�bbyVnC�a��vojB�QB������`��AQ�����!���eT	�\k��f��N(F��]��`�8���:���4`y�D�+wn���v��� 
1��=qy�c.M���y:i!�TY1JK��h�;�	j*y���CZ�4�R�X������h8�b�s�o���!����;|H6������C���R")/2RqN5K��c�l�����]�aHa���j��m�3Rq���1R`uI��n��]���G]!��6i��4_ZJ]�������}w��S)���Zk��C��n:�XFuK%(������NS�MM�|\�f�T��������
`~���K%�]���>_9����������&�SF4���k�*��d0����fm��Dqv���h�
Rf������t�3�_�@3����_��!�3����D�v_�xiM �G������~��-j%�������$0�^c]�G�(�/������q�^"d������!_J���r,��IX�al���f���[���?�#����' ��������U�b�Y�L�����X��}�mi��RU�����<�����M����1w��oAYR,m�2o�������p����}��{5����F�t&*j:�=����)��!e�c����P�2)�1��S�f��CQ'_���R����O����5=@�Q�;7������1�M�f�����E/����(�\�B������r���U6��,W�qIh�����#B��U��]���%�h`XS��}�_����������K�A*�M������R����Y|G\�&�iR��	�������ki����r5�W���O�����G����6v->���(�5i���k,�`_S�����Z���j<\}\�s���\�l�Zt������J�)��U@l��N��o���(��W��,�z��0p��(
�=Q@M�9�5�:����c�Q@��ro��-��m��Q���Km7���^Z&�������Y���R�g�=Z�r��_�m��#B�Y����4
Rd���JB�k��r�c��HC�T�E���`�|G=���.�RZH"��-~�"/s7��Z��D�lu3����O�-��#j���Eoi���)����c`J�g$����Y��*x|J�������z5�����Q�
����--6Gk
r���X��I�r��Q1(����*J����O�ft����|�1�r�����]�S�|.�u��&9~���:�O���j�Te-�l�c����X�J�8�p)����X����������j<"�p�5Y�����)cpV
MJ�m��WYx������>�BLeg��H��t6�o��q���1�����:���k]�4y������k�%�3�����c�j}&�����M;���"�1����=h���c}��T%���2�����L{M�
��K��h��NV���B w�
����9,0����c�`M�384=}J`�iH�P����U)�����x�8`{���z~B/����"?�mG�hMc�����P
�[w<3����B�O�^�}i�p!�T������[�������`�%�b$u?����0#e�eU�JG�@��Z,����uyUr�}�c�]��p���f����a���.�RvIHI��~��.��S�=�hS���kW�1[�>Q�d��=�	!���Bi��}@n�s�Y' -�x"�!�:o��-�2�|J�
��P:�>�X��G
���������������<����8#F�T6���gv�E�|�l��.�-��6��N�~����1�2�<�k��|t�����L
k����z[$�q�'D�����>���������[�f}�j�����nyo�G3'�hIF��8���3Z�Mt�jO�H�Ze&�r���bS��{��X�T���0ox�;���[�g@�b��Ov�!�:��t����q����;�����Z��}��ib-��C����0�9��g���|�G�4�13��(��&V��\]y������U��
Fpiy&�%��F+�A����b�
���
^�v�z�J�b���`��(
��(�)�tt[��#,��( �QMR�T-���y�a�-�k30-�V�9��Be+���?�C1/d%������X���������#���y�U�=��B�FN%��{Kc����q�g�;=bn�!��n�����ko]�%��Y:�bAJu7U��8����]��J|q.]�~����c�b}�����]�d��27��T��1���k��T��3'��.0M�����G��e�P���f<}L<��)c����<}SU����N����4�
��zkZ2��4,K��9V�����?�J���w�a�?� �:�o�������e��-~�i��!��������z�������~��w���p,#�T��_UW3��T��#,R��jz�n'�3+3����LQ�Gz�;������E��V��5�����]z;���
�4���;�z��WDq!o&����2���>��������QW�:��?Of�{>�\m���lO��:��e��������/8���r��a�1�U������������%e:��lw��CG
�*���������-a�%�����X'T=���6 ��r������/]r��"�g:�iOj��	(R*�J�����l���Y�wcf=�c��sfd|&V��w��eCC2=��4T6�����h��W�������*j��fU���p�!b��������s,5@����H68����co}�U ���-���1�:�109���+�
���l:f�gd�lY�dR�!��Z.�R�t���X3"�S��_����N���g�$xA�l���DH�~$BPF8��k@"��wy��<!JS�8Qg�z�`[���g���|�0'���>����!��v�fy~|�	EM��-6����_��4�+��k�)M�'�[�H�b�V�L,i��0����L9w��x�L�sj�+_M|�2��
������Ln���<2�un���}�q��_Z�AC#6{�p,����3jI�V��������z
�����c�iu"����;�J�QE������0�
n��N�ZnOjL!���<���E�2
�T����5�f��KS��Kr���V��*n�5B�Uy:S�F��xS~�e�p>��w��8�`O@O�]~i�����=Gu��~����/K�V��#���i�7mb�g/h{��y\[=%������)W������bon�u�a��=����
,qQ��}�_9P�
"�Mw�6�
��co}�It-h���[��<�~�3`rf��B��N5W�1��>�d��,j�<�Z7�T��\�o:��E�����Dm�3#/8Mo�����!���8D�:
������������������u"1y�d"l�N�����+��F�PS����{FT��/;��@l�	%-U�W���a�|g�
���MR��Q�=���n������9U����h��U���XM�X��a#���e:dU{G�+�A"G��N<.a��N���9��oC�`1������jr�����f�G^��2��VL{��
/�4��,�cX���!��lG��
��L)�Q-\F�j[�uBU�[�Wu�g_���Z�X�����<�		�QK @�!3k7��(���p�4P�E�y��C�)(M,`uy�L'�qG��Q��;&���<J��Xg`�A�4�r�<�=�C������5��?b(����9��h�I���)YM��T�~]2P�H��U�;Z�y~�i)��c��%[
��w:���1�<z��L�8���c��Foo:���X�B�z��)
����z�Y�&��;Ad�1��>�d��,�D#Nq�������-:F���X'5�����gf
�4%�����X�)�N�D��D$�TB�rh}%$��.��X���P���6�������#_'5)=����`O{`2a���MOT���@�t���������	���@����&����(�lz���8*�c���9���J�$T\�DV=n����`)C��G�R�(,6�����.�$�_cP�mx�c���o9���X���g���Dj��''���������B��J4�h�U�y�QJj��L�]�z�����������J���-�4
���L������DP�=�*���A�[��t����t[�uB5+�I����RnW`7a�S��s;�$%���f��'J�a~�B:����V��A��F	!w
RQ���V':������9{�N-�	P�%���2��������c��w6��L������)E907��B-l�nF����.������iK����2a�co}����,�L@-M��_hh���e\�65��c�a}�����;�Hy.�6��s����W'-x
�)�����u��0M3�����K������]�C���_���9�l-�*=���X���L�1XKs�t&�4�kKe��X�:��+!�`������1�jN�p`��-��c}����)$�"�
��7�����!m#��$�a�}$�,��\f��2��p�����I�#B���W�p��G@� ���[{m������rL����rC�T�Z|�������
�����tl��x�r�e%1����#K2�O�3�O�4�+�A��DK�R�<F��X/X�_�;cA(��)�������F��\�T�	���t�F������p�zmk(� �:��)S�\3j��)�l,1ci����|�����t� ;b�J��B�E�������R�������
����OH���mN�t-j����]L�����������D�j%a2T����,;�:I`"�����ECE��z��W
�)���h���.���Fo��������Ac@uh�C�d^�i���J���>F
R��/!�7���H�M�nh�u� �zq(��a�A[�2	�2M��������4��#
��+�A(�(4��!08q��>�M�� �:q����%%b-�l*��	���E���J����e�C�������a�Y��tz���X'�#�I*PK��w��S�g�����1�4T���U*M��G�M4V
:����!��U�5]MG/�!p����{�S7U�-�Jr�2F{[r�}�c)C���,��
���V�[��u9��,f}��M�&����K�.�U�0B�wi}�����P%����DS)=���T��!/����F!dVH���&�K|i�K�MRJ]w���?�:������M��r���9mt�c}@5V��i���TZ�4@��X�/W��8����d:Q�=yu���+rImS���+[�u���J��R�=0M�����z������p5=Z#^�g�jz4�-�1�W�Q%���U�M���(�c��
Q8l@���}��M�I���P	�=��r���1S���3�������<�i����+��N��K#�>�d:f��d�lY��E�@@�7I�1�R�l9��E��4�j�l1���������K�!��p�;p�0���2�S�8��r����1����ZF���bOX�?�U����(���O�A��@s�r���?A}�}H�Y5*���x>�������E���|���B�������X�	/T9p�by,/�V~��t���=~7��p�
t��u��b"��-�����w:���1������
�A�����st���A�������ps����b/�;I��nTc6�G�3���s�T��>k��P�dl��u��C>.��zL�/C`���$�/�%O�y��a���)E!Zn��v�>:U���DM^%��c���p��4o[(V��U'�����! �N`O��Y����g�)�so��8z�CX��Z_�_�MO����fWd������������Ue��]M��Wt!���x5X����*����I�Rd'c,u�,DL1K���s�Is$m���L���[�cYN����zJ�[�����;I����]M�l�����-K{���*��\j��:�
r�������j�!�$,��4�g��h~�Z}���cy��h�<H�c��8D���X/1Pn��\Ck�����f,�^�#�*��.�uog/�_���@�\9���r_�G_���x*\���`�z�b�lL0#�_b������"�.G_m�V�X�����h��Ses������end��bG#���$�5Vo��FM.:���1v��`��%h�fs�A��u9�A �d�5,��R�����x�?|5�O�3�O���k��S�
��xs� ���j��B�)6=���O�Laj��+�J��l:T}T���F�����U��~"���4�����+��R��/hT�`�L=�N%��(�N;��)(�y�%������) ;1*K�H�&zT�A��z.�X2��Z8G�~\a����?��������������dz,=��o�?<���#��!Z�����1c�����D�0&�+����-�|S���N���G����i���.�%�|Oh��������So>�p���r����mX�*~�ea��R������93|�c�X4��$!������&U���yY��oC6����DVL�Q��HX]p�pf��>$�R���,
HM��,H|m����Y�K���A�v��cByBCx�=� �:���(�A�K��B{�0
{� �L%)���GC���|��jq����X�P�������Gu�dc�&.����::=iY4���[�j��c�d���]�a��P:Zn�p��k�co]�����bR#Fiy�qS��<����b�+	����}Z��}������Y-j����o>��XX��s
������*����y�������,�t"��.�c�(L�enW��������B�=��Z�P�	_<#s2��%��F�l:Q� ��H1��C�e�:���l9�8�!�We�(N#8P�����/M���	iK�^r^m��@[*U�W�c	���j�d�aE��*$��vO�Q��o����Q������rHT-?C-S�@F���r���1NG\N��V��~�<�%uK��+m�R����+�����I�'[6��"	����c�6-��hTY��X�-��	h�������I��fP���5�.�\,��!�8�2WKe��t���d�Q=Y��<������(i9�bOx���W�dI'�@-/�M'�>����<7'R:��e�}�}�K��������oN��M�j+���4/�P�s��/M���I���X��/Zl����]M��E*��^MG�-�"��p�����T��a@���v�������l����Y�WI�{�r�"�����o"��B/��Y��P���l:f��'m��i�Vs"M4'����J�`��,?��c\x�t���p�/�<�%/l���l:T��H����gn�V��&�[�uB5O[%�|�6���9���������K�����r� ;"@
����E���Ks���X_��j�H�ZZ2	,��C������Zyi��A����U�����d�P�W�cY��*�,���#B��Y�U��}��(E�4�$�)\�����c���h!S�j�@G�� �A��u9��!���P�G���D�&�Yt��y�Hu��d:f�'f�lY�����E#L��PJL�cqPk�-�:���������+lJ�����>�%�v���N�D��DM��D�����Xnp	��D�����*��P����4
�(����'��h]	�l:���s�� 4�����[~u"��7�-�Q����F��g9u���o�C�Z���f|�xA��J��K��*�=^,G�/�#��u���R����VQ�,?�i��?�K���}�Q�����%hR*�q���1����G
�E5���,�����W]l�B������}Z��}��aXAS������(�+��~��X'X��R�F1�-�O��rj���������AwA�� �1��T����������~i�-�Q��3j�_�^�r
�-	��U/����9@������bc(�-��b�`17+H�)�����3���w>�}^�hiyP�<����j|����n���DK�E]����.�L���]�h��3&	B\
J��������t�}�cHy�kV';BK����=���.�8"f�HHK�|����U��To��C��!H�|5�
�S�O����"�eb�Y����n[��bQT��1B�(����y���2�
K���~2����i�R��-�Cq�l��>���1O	�n�-:���a��h��R�@X�1T����a��`�bJ=bnM�f��A�u����f7L���f���i�H��g:���4T����V>��+EAY�
��Q6=�^�v@�j;�5��U�+m�!D�������>��C���c�����Q�Q���*,>�~{���.��4\F�yk����0��!���/�*[P��cvju����������Z��V:<��u���M�:��s��tFhyd������T�d��U��v�j������s�l�t�����X/X1D���!�R��Xq����"Z�\�YZI���E�0X���!
��!����E��Bl�����tk#�RO��6��2��P�������H��������X��|�Z�j�d���������AS��Jb~=� ��n�������Id�#yD��g@������<3L��!	Efh�_4�����yy�\���~I��Ax�����O6-��#G�K���DG�����9(@l�#-<M�����G�������DP�}PD�}�R��rI}y1�"��4��	�0�j��K����i��/����_�������9m�.)o.u;(��!"�M�z����'���K���A�y@_��[)�N�XR�.���--\�BT;h�j{���sO3����m�x�����J�#��I�U��M�P����{�6���~�����]��1rJ�H��%�#[x�k�M���c���Cl�!�v���)_T\���P�+������i�'{�����xH�V�\�55�|<X������3:�hkt�S[39T]*����������b4�M�[�t����������b*%��-#Xy��`���Hm
ZBv����v�(�����Y�b~$M-
�2X�L�t�/
�����Y�4O��_][SI�4��2q�E�������P92�bz,���d�}]t���,T�Zl�$[O�T���d��no�F
�):���1���X"��<j�%����{�r�1Z~�8��k�tSm��}���9�4�bin����j;f#�'f�lZ����,AC(�Q������G���(�!�C�=[�8M4�dj.�QX�%���X��N�E��EV�<f0����E^tH�M�:OF��b�M��9�Y��CQ%_��I�ky����8�N~�ss$`ij[�by8�i�A�E�:�����M��Al�R�����
?������j{�ESm�X���j{0G��Q���#B�9�Ul����	������y3�f�����]�An�������p�c{�[1��2\I��4}�s*sg�:��[�|�W��
#��y���n��j�W�,fiJ�txLG���*;���yDC��K��p�j^���["P�Ut��������@��w�w�J�kn:�V���[CPjy�����e�Q)	����v�(`{�!J:�H
Kc�Q��hH����c
)�2��[���4���UNzi��i�PkQ�Z���fK�]��64��`�Es��������"e�e��ZG)��.h!8��Swcf�T��t�}�cA��6�	�E�����`���.���|Q�c6[��&��]>O����[(��)W�1�>5�d��=hte����(-szt�1Z�-�:�(�������8#9��/�"���U��t$z��D����>��s�6���C"�X(e1����M*��4���*���-��\�~������~y9��Z����K�-�:���a���;��k?mf�3���wKm����H*|1w�|��+J���������$y�W����vDx�"K����;ZC�# �������,	l������.�1����4����nO9���.�0jL%��zM����P����^U�b�6�[m�������]M{�J�C��Wh��Bch�-�z��"H��]�
���<3XI��l]�u1����L��
1 nx����o9�U21����5�@�l�i���P<��KRD��s��Nd��-*��<�F��[�uFf�T5�D��x�i���#\��|��"�%Uj�s
o�$\�9T�}]�&]|1�|�v1`�L���^�h�DS�D6��������Yr�}�c�#q��`��e�����{�r%���J`o��2�Vyr��q���%�����l;f#�'h�lZ����k
0�iA��9l�g�X'=_�
y���4��������������V��N�D��D��N���o�&�0��N$�6��-M�r��'��^�/�*]
�b�u��j;���gl8�����,0F���X��5x��2b���/���R�R�"�.�[f��������@]$V@�K���%���YmG/�$q����NL�0���hD����3��.9�����H��"r,�������<[��uy����G	R�e^�I^����JS�SD�%R��cvj}
����]x�H�z�7MT�19��g�x5"pL	*��r��_p����W���X%Z�����5��<A��`M������[���� #mis��2Z(����)l���l���NhGD�|c�Rl��(��o9��R�TA�:�E'k���@�$��f^�Z�0-Z�9�f}�@�y����Vw5>�{��x�z���j(���bRhY���)S~	���2�������Z�{��X}�8Z�{���Vp�go]�!q:��0w���[��uJ�R>�V<��DV�1�>G�d��.8�x���)�i��Z4my�	G
1���3��<F�C"^���SJT�l��m��D`�]`44�<�s��&0n�9y�	F��&��2����{0�P��W�T�-������j;�m��UL��edX��G�����]��/����yy3#���z�����J�<�-^I�|3?V���Vp-�M�3����,�K-��1^m|��2k��|cO�@�UX5���m!��������,DCU� L����]��g�����bZ��Q��t�%��
��*V����T����������g�:�,U!���:oiJ6�������B,����'Iy����%+��[�xu����N��*��EZ��?��g�he��H�.�]C+��n9��T��-��f�\���6V���>P\m'
�+P�<���uD���"�8��2��2�O7K�E+I�B�L����=�A���q�P�����H3���^��%� �%V��Z�G�?(QG����{s�s���ch������*�z���S*��K
���Gs^m9�������/��kj�!a��C���|��"��<V��]��l����mK;����2��1��
��Cn:��G��>�G
-�>�����z0J)N%�s3����FJe�82A�vD7;Cr���HB�N}����0�NC�����`a
��~N���W��`.;]�`�Q5���E����C�QS�
�s����=t}�'l��D�T�5/M���%RZRAU��^l�U������V�cI��Pm]s�\�L��5;�K��)��
�%�FC��z�����eH]i�q���`���.�82���R��k�S7�V=O	��Jd_�^[V�1{�>}�d_��z����Hh-��r����+��l�u����
��/���/)�A��B�
��x"���jn��������nLo�M�:�Ws��
/�mT8S(#��/?�R%.��&�O��c;�hn���c����0�t���VIEQ$O���8�:�K��'���/��4
'r\R�^��X���HEZ,Z�7��f^��T$TR/���CE�����{����.��jzh{3�%x���]��a(��0��2�p��8����Zs/�T�Q�3&���n�R>J�B1��JE��x�V�O�>����!?*4�[����$i[���1a�4OFC�$Y���<x|F=
z:��r��x"4�42�xy3����Cx�1�|��<%��L!����p���B�,h�����j��G�Z~��x"���X~]����7@�R��Xor������e\=<����IE���^�1i�S!�M�T�Z�U1M�q�S�������%���q�j<"�p�1Y����d
b����-��G�8h�c���]��@Q1b@-\�����[�c$�/���e��DN''L���[tA��c��c�j}����=xM��R<��4�$W����"�����@8*/w�J�NS�MM�|ZSN�V*ko��Uw���X���&�%����WX9��8��r������OXq����Z"\���[m'
�#�KJ�S�-Rw����� @b.n��8����6S��@[O��~./M�4�@<�R9y����������%�rL���h�
.�X�x5d�L��r_�h@�/vb.�Z��,4F�Qr�}�c!UK��PA���4��<��r���14d�������S�@��K�R>�r\y1)��X�����X��}�m�<R
���I�I�s��{��N<��	���4%��z>�B,R~����W�y��aH�����Y`��M���HJ����eSn���%��&_%T!�T=���|���x"���c@�,��o�� ���c���BhQ2T����_���:�O��7�)k9�?�p���q�j}�TB���*C}�>�3�TS��_�G�-r&��|��oSM��#����o��c���_���V��S��l�@��[�c(Q	�f]v�pg�|��<E���U��.PK����j<f��'r��k�X��=�r-�U`�1��-�:��*��1z�<�EOVej�����U�W������R������J�:���r��C���
�.�\�	G�����rN'�Ke����Dq@v�"c����ri�����c}q ?R�f����<<���@�C���B������quX�K���B7�c?�17��#`V����(��6V���0ce�eU�bG�����]�d���e���M����\��h�d��4f��g�~��jme2�t2<Qy����U7P��@y���D�,���g:��s���c�c�����;��[y�ci�9��c/�C���X3����O/<ZH��8s�wr��X�1��s�b{�����<F�7rBVI�[���L�������:q��k}aD�����2�F8���1�A-�}�?�M{��Y4JN	
�����h|D���'�%[a-���a�������h*�V&����0�����s�[�YH�<��&Gt������������IG����(����[_�[�$Q��j����w�ky�kn~Z��g�\�Mh����9N�aD�a�/�����8��Mj���9��-��G-����&y�r�3�Hv=sYz�u7 2B�S�=�r�t��h�
�7�9'�=���T�^���q2N�����J[���
x%����]�
=���9�+�n��8�O��Bx�1�\E���}x�#y=��$^K�l`eWb�2�j������W�d�����w
f7^(�H P��@��A���)b�;�	���S��gco��q���i'a������X�2�����e~���$���8����<�o��0�A+x3�l����:`:3�*���ZV���A����S�h���A���E�(Y�Y{���Y{��\{9�&���>'W����t��8��cJX8�	�Qk��9����V��������=]i���#�N"�2R�p�0Y�������G��H*�[� �/�G�#y&N`�K�Zx�<2��}]����+\��(�yx��?�U[�r)��b�M�q����B��@Y��N=3�J �w��PvwDEC����E���y�z��B��������]���_n��_��/��/W���V���O�V6<�^��#m"���>\1�V��No����^K��;��������S�*�%��=�
��B���;;�6�"��:�:�G�)Z��{����)/�����s,��<-nv�����K�(.�}����������s���~u�}�
����a>!�wB~�i�l�fF������f���X�A�N�6L7�u��i�ZMP��"I��|�V����i�e������N���B�~�:W
u����L)����I!�j�d�O�p����:B�s�����2S�$�Y��YX��~�������{�n�W3,e�hkd
�A���f��YA%3���S�]A%o<E�Ru�u�10�����w-v�_;e���c/��1��	���{��n�4�����)�����f:���d_Y	2��(��}AF��':v�	��!k���B��K3(wC��f*��.�D@��Q-�b����V-��~����$�B���� �T�Cb;gp�������P�K�S�����O��<)��d��vECt������}����
�H"�Z��� ��m?�O�J�G1�����3*o�W+f�u����r>�j�*?^��uX�sv�5wO��P'�P�:�4rB�04e�B���1�r��.2�t��<i��c/�Cc�T��t)f��ff<�b��k�,S2�����3mC`�@�^U"V�:������X��v!<���O������7����G�����s���'o8G�z��Y�rb'��������|?���3��eJ�.| D��Q�zFR��(f�;��^(k7<r����}`������8]��ZC������Z+�'�k� �>~�V��p�3
U�����='�H�\rv!���k�A�^�{cV��d�����F>t���cd��Vve���*W�-�[bqv�~o�����8���_XAbJ%[q��dLf�09v����ck�)?���Z�v �JH�$�����)#v5�fs�2G��E�q1��o
B q��E�|�F�q8��t!���%��S2K�����s��G��M�UH�(Y�]
�_��A��{�-"�}�����G��Vo�W+N�u���jF�������)\�DvI��{�Y#:#R�������s
��c��� � �&��K ��J9�r�1P1����j#����T���;J����3:;�9m���8�2����n9��y�v?��9���`Me@�3y����
�t}/M�|#T�yP[1]�:p�B��4�]?����@�0gLCx�W\�2���, 91l�V��B1�F���H�=t��Q4��MJ����%�)�k�����Gr���b�i�i����������D���+�W�l�z���+�WS,�;�n�3���)�]�+g��a�kE�"��Y��8idz���1�T�I��3{&P�&s��G���p��fKo�Q����vx��8U���N*G���Asa{B����$b�.g%��aOt�����m�m���.��ip���O��BRL����J��
?���l.[�3�����H��\��4���*9����"G�Ot�k���u�O�����l�I��A�s��������
P��k��������W�����o�W�N��5F�k9������n�3��V9����=�)?�$��5X'�aV�J���1�L0a����g�,��9�r��R����-{�Vj1�\{*t]���Z��1����s���}q�i$
)�R2��Q'�t�y����w,sN�5����Qy���
�:Ts���#T�t!��T	��59(w��tx�Lr���
�5�W�%�j�e�������R<���y�:)H�@�@�l������ ���'9v6HD�+�|�\�^kQ��W^1J��0�4�����2�z�0���������rn�jk�?rCa:#�Y����=��/����'������[s�u�1��4�&���=�V�����#�^�9��V#GN�����FO�N��z��.�8��f:����_Y��������z�C�lyN�x��I,*+�h���4�n�4P|D�LY�}�7��`�`H�e�+#bm'���u�s��I��3%�]�^��u��[0s�0��)��L��@�PMR�]7nt�t� �Ep��s��N�p�P�3���]��@�����������v���AR���Z�������J~�� ��O���DY]r2�yr�pN]Rs�u�����0%e������9O�G~���4�9�- b]�����*�����w=��<�q���t�9mO��8�0��w���l�����)�G��+d.|�s�c;�az�>�,�����o�!����8�]�������#�N5�Q��)[m3��HM�-�+�V�H��&z)���C@�.h$
��u�D����9v.$���k���^�uv?�����!��8���Ch(�W<��Y,7������������uze���Oj��N!��^��YTQ;\c1����cfBf9���\f�Fr9�r����Z4��	j>ki?�X���=iSuY����s�a{F����,2�dq����C\b<k��c���)�f�z�s��ox�j>S\B���SLB�� ��<C)"��8IJz��Y$�yR�(��������#�5�%��8��r!���Ir��g5��m2�
�������e��������?/t�L��+�Q������7��%'��}�7��9��*9)�>��:'�kp����H�#FB�J������U�^�S �l������t	:�r���~� JK3��=����}�q��������%Mi��s���}u�}�o�`���=
����j�c'��\����j_�C:�:�8`}R��x����:P�i������$��]�Ic�;U ���bO�X�z���bm������I�Iu��#!���./s���X�Y��;H(GBe�w_�e��?�n���DY�����z3���aT����7�k9�g��Lg��r,y�����(S�j$�f���>�+����c�C���2�`�����4
��r�������"�uN]�G|�e��3,wz��
��@���9��)�G�F��4��H�z�l`1��;����L,A�X=�4$;���8X|D�I�K��o�!�����/*-�����$��H�$*X�G���Zr�i�e��G$��:)xn.-��B�����}47�i1-������i��E\+�����HZ�p�n�6^�'��n.�����Z�	m9��C~�|7�k�.Om��f:#�`��%����WJ	���PO�
����,�{sT�J#��LQ �t�s���	��$.{Y�{K�]Y�l�E�87�]O������!��9��=o��L�X�(�IN�*;��u���C�N��)D�gqIu�m��2�K�'�UjT��L�j�*P��e��y���u�d	���5w�r���q�
�	��oW�������nk		��t�T`v��#1�# h�^���'�H��D~�'���
����l����Y�y���w3��d��sn��n�3�
UI��~b�,���(�(R���Q7�C��{s�$�[��g�Y=�����c/'{�����f�"Y"|�Q��)@�h@�m�G��9��Y�GF�HI
S\$�K?X��c��Pi�6�
�j�p���4�rO$�e@7����cH`�l�w���K��CH43%T�a�ZH��8��������i�H�����LM�����#�����A�YO�a����nE�?�����'�[��O'7�k�'�E��VA�L����q!����-\gFv���e2"d���&��<�l6�J�9�:��Y��(�I5�w2�bF�{9�Xi B�����/��p�T�8����Ork\.�sNi{������22�R����������������(F��Zx�t�e���&N�����\L���@�T��e��������������:����3(��:}���v*!O�%��^��dl|B��.%l(J�]���W�}Nw��m$HH/!1�/H�s������i'b�eb�J��eA�G����>����?^�m����ZnH����o�3�����]g�'F\9	+DY�����p�dN�N������a�T�E:V���q�I+�{9�3%"����F��F��N�Cwz��-Y�}�f9����_X?�D�����4y�6k���_��HRVQ�����h�l�i�S?�^&"|n��p3]���9�3�L}��k0��Lm8r�$I@X�1r��B��P�����#�e.���������
�FT�*�q��d�����v��Ix'us�q�����x������c���f=��]7�j��^�����W1����
[�������5���M<\~��=L=�w��N�z1���A�^+���2�� ]�a>�#9���S�E��5n�D�:��Vo����S����v)���#����s���}q��|
�2'q�2���tq���t�%������R3o��
hq����f�Ry��CU�2*wHG�6�{��H���S��������e���&OV���[G.�L�24�g7���R�5�����1���sAR��4�����4��q��S3,�
�
�q��n{�,J3�QL��X"]����7�a���������i$)�ukvL	��	��~����dh�������%���p���F_YyJ��������X���m��da9��gd_X@b�d���D=#�42�9�G��D"�ITvP��9�p��� ��%V4���b���|���fE6/=,��Uw��(�4GU�P��}��8�����bG�k�m��&:��.}�~%���aO�(y�9��#��A2�G�WF����������t��iLm����&Ezv��&����w*�f{����h���L�%E��Rk�����Lg��\%E���-?w�KBM��Z&�H�	���/k���9���������#�lN���c/'+���i�:�������S�=����
Z��y��x���D�Ts���DY�$�����I���*��3 %����H��2o�K�&��S��k�n�!G�
l�G�0�]H=�&9v��@�Jk��3��>���'�U�]ueu�6��f�P��`qU�@����l�9���c'C���(_Jl�9���9|F]1��Yi����K����7�F]�n{��-���u3��b������o�3B��I�]�'FSE����H��#k�4�����:�S�KM�{���i'~N���c/'+�eG5E��!�Un�4��c*O"H�$����c���}qde�TFI&g��Li�9|��c'�(eAk�d��u��eG���=	xqj|��.�E�"��8��3��$�E����X�����w����U@��$�^�d��b��m��h.�0��+�?j���I�� %���~���������F��������������ZJ�[���n��$q��4��l�? 2���ae�������E�~��t�P��u�/Bec5��g�$��u���	��>�/K�g�,��W8��('I���;�#��}�������������nT�s��=������I���G`M�U�/<z�>�%������v�bk6q�����T�Q���r��I���iV��I�glZ���y�����o�����.�E�f�D��VU�v�+�|H�Y� )����~���b���im�yn��C��q*�>�n{�����mh���b�%G=�*I���-Pe[pW����Q��A��$������-���5�^���L"n�Ir����s������p���q�R�����,)�>M������Ji$jo�sb{j����4���K���:���v�c�������E���e�8�<R���D�Xl�"a1Yi�E����X����#X��0�����*�x4c~�2�	��oS��G��)��.~?G^�� h��F�3������7N�^h�����6~nU
�lG~j������~����������ry�����^���l�w���vFp�:K�y��4�����C1=�E6���Ts�u����-�)!�����@�%���p�\�Q��y�^�r<���'�`Y�X���-7�9'�={��T�Hr.C�9	JU��y�9��#�N�U�oQ���C��0�>\�R�R�1X��w�]�y�����Jn���
���;�dq�b��]
��O���)J���Q���.|$
��KU��g��.�����(5Q6,��6���(p\�:	�v���)��Y)�`��8�����R`cn����^K��l������{3�b�J��.��3��R�*5�P�0�Y-35�^+
|-�UQ����!���z9�W���4��SMaV���W?N����2�-���sb{�����!0R���r����
g�O<v�$E!S����"C�m��b1yc��n�q��5[\�j����]X�,�J�
�k�pjX�e�)��t�(�X�U�.�����q��n�Px�����Y�G�����
DV����������m?��\J�r-����?��n���z����ZQ
����wr������Q����do�3����]�L'd9��w��$5�s���_�~�M�,�W��{���������r����0)�V����7�4���jjX6�V&�G�������S~q�e�et�S$�F��2�as��;���S��w
�e\hv�������7��a��B`��`�LR2������Ox�d��w��+E�kA5J�IM�R���I���.C�[o���BA���`N���~�Y�(@�l (Y�"�9k�( �R���@
�H=5���S�
�6�����^���?�n{���466���3B����]�L'fcC�����s���t�o0'f�{p�P�I�	$��
s~�y�_/�����l\$���xX9���z{X)
C����v�Al���8�~>�xV��7�I/=�n�5����s`$t�"��a��:�8`|H%M>��(�m��"�(j�,Q����O���F�9v�I!K��t
������N�j����3]�0}�D9g���e�����w�e����]�81/��i�P�����Gz�����ml�y7�V�RT���.��2$7j�m:��vFp�*C����OL�b���R��TW~]X7��0�{rL� 8�$���pk|esT�G���p,�����4�v�z�M������T���1�]7�9��=w��L�y��e%�(V{fX����u�Ta������`�c�~������6�����BX�����T�Ut(=�0��<r�$X����Cr�Q�`T��=�(����(*��S���.d 
(�gp�Y�'�����D����<��/=Q ��W�][��=5���s��8N�����A��q��n{-���Lki�Tv����l�.��c�(E�!�l���HiV�Ts�u�1+�-R!e���}��mz0�|�c/'�������PW;A���u�8�r�Wp�-R�F��f;� �'g_Z@��&��9n��:�Y��;��D R���o1l$gG�����GT�X$7���C��t!$�$J�2�����y���yt�c��9�1J������,7d2+�IO@�|��lC�m���7����#�/���b�u��9=�G���1���
�vq�����7��uh�S�$=�=7N�m�������HLRc!q���$q���E��mgD�\%Id����qW�"�x�1�3J$��1%l�{r,n#�(��g�/����I���p,�2��`��K�����)��T��o���r��sR���/N5�(�����ge$������sh���=	#Z�����i��=�(�aol��m�*�`2���l�q�h������`UJ�(�U���X��p���,��I���h|t�m
4�������=�t�;�@��4�[�Z���Q@����:���Q����"��j��j���C:on�sRv�k)�����N�����u�eW��������E�A����w����c�C�A���hD����H[������2�����V%f�2�����1�)�r�l�o�sb{�����4J�-�$�m��d�{��I42�71WA����
�y_�T���5z�]�:��Be
��VUW��s:d�;�E�IL���g�>m�3+�O������������F�OP�+k��t��R�#�N��<�d00����������������\IG���}K�����V��T�Z��*�f|1[��I��w��b�7�i�����z����$�S8jQ�Yv#�������9���
����������K�������L�[���+#�--s),��|��wi�R�6y��������}u�}�)�a�9���eH�4������xE�_��8�t&q?���J�������U�ZI9r�,�OG�����$�N���������d���?�p�v�J���5���0�f�P�����$��^�Cd���C�NF��\�wd&�39%*R]���W[P�2���^�V���e�I��z��XL��1�&��c7����-���k7�f����.����R�N�;JJ�Ax6'~�{t�=#[6�����1��T9t���c�=��5�[�|��e�Eg_��:�e�P���x�QlO��8�4�G|C�'������y�_'��2KH�)����e�����{��|oM6v����C`�����ej�L�w�y�EzK) ��^�����'�N�M�By�mb����B����e���@�G�B�+�'�u��}K%��>B�o���i'g�N�<7k�3N%�;��u�?���� �f�(���^�� ��q���vF��:g�K}��$��M$G�q#�v6m2���y�:�Y88�Y�gs��#�^NxV����Dq7�$�q5��.��|�+x2e�Mc�������T��smC�UJ��H{v�����I��Dl�V`���� �6Z�M{ib���
����~����Bh�chev%p2������7Z�ZS#�e�IW�����*hm?!y"k�,5������q_���B���BE23FQ�x����=T�@�Pt�)�"b��%�4������4=<��LM[�����;�,��]r�n|�G�R�_���)S����xF��*{d�n�N��r01�� �z&a:��t�u�1�8!c�YN������8�GC&9�r�1���<���Z����	.��aq��N���y��q�<v�9G�=�����y<hR�$����5*n��Xy��9<21��cr���hE��������{�����1����B#�G#���	������yY9r�$�����{�r��N�U�������5��2��
���B0��Gn��-n\���
rAvO�aNvk�����M����Jq'mM#a���HGE/,Z���n{��&��-����w�kYQk���n<#�p���vb��Qh$Ne�/w��F��)Vj��8[*�A���T[��9���c�{9�'��
�7���Y���)����n.�������}q��| �2��"{S����x��{���sx%�$�,A��9�D�s��O��,�����v!��X�H��~4{\G�7�-��9v��"�`b1��N���Q����jb)���/��x�8`q@@���A�{��.L�Ut���8�9�Pe��=q����<=�iWQ����f]z�����o���Y_�!��fh�<n��2/��������#u�eW$���Y����������n4���9�:��&p�o��rO�F�?g���_/'���T�Bb�a��y�1J�Wy��o��R�,�{��������c���(��SI���K)n�Ib�#�N��Q(���� o���������(9���������1�4jV"��E����#�p����i����(;z�T��D]�o�]�_�m��Xi�/��8��c���;���l�8r�$��B���I�g0`:�2V�����mEOM���,�"-�����������!�[�����^K��2��*����/Z%K����'�f����D�$=3,j�A�^�#���B������Mv��q�c/'+�Y�bf�e0Sgu��L[�-��go��L��w�9g�=���\�`�%i�Q�h@�n����c'�8�T�����+m�4s��R�b�[iIk��n�\y��50'�H zV�\eN�u��9��'ry[r��k��/�
���[�]�b�������x�8 #q I��uQ?q���f�c'�@����������5y�u��SS/=����h������i�0��37�k�/�5��@z7�d�N��j�|b$X�Q�P)zz�*d&=5�^�C��29K����a�sz�{9�XTs�����������m�}L���5�QN�n<�(��i_[�# )�C`�}x�9���c����wI����6]��{ir�^`LW�6�q7^�y�e�@�����r�O��f=V9v�*���R���M���	�����d�M�F�����/�~A��(E�����(:��M:r�$���j$����@�<�wu��1&�#V�'�D@q�7��[_+Wq��S���w�kH��Xm|��?��zF��U��w����6[���d4�<�z:��4g;H���!� ��',��n�����Rf�c/'+%��G��S��lE�&?���v�8$������~Y�9����'F K�V�%���iPj��,@���!j�s}�y�e���U��Z��������B���bV���0u�S��
=r�$`��\%J��eE��(�?�z�v�
$)��kC�>D��z�H@#�r��<���LU"�aa�;	��c����<�B��wjj�zn�C��-�E�l���z���CB��eh�����b�DS��|��h����
`?1�*	zvf,��s�z9\z6����c��WNv8�q�\���	������Ry0��$*�U�����;=����6k
�������}qpe$��F��Qs��'��j�����DI�%� v�m�����<����%l|��e�u�E�x���G�,G���#(1'&�]O��2{��(��,��4c���[/l$d5
��k��\R�<������ ��)Qr��e�����M��m?����l�MYq�T�Yo��e~��%����7.M�e}1�BN[um���q7�j�g�4�D��U�~bdU����s�l�{����@�A�^�(j�������-j�c/'{{dK��H��-���8�m����������sNks>�������&�2:��v�~Y�2��������rT��=^�z����c!�MZ��[/�WIi�e=�V�SW�@S��C�N�5�ZI��Q:�/|��'�_�_�B\�������	�~�������C�F��l����Q?�G�Uc�>��B]�w�a�y��&bz�@R��z�����b����qcc�/�k�LHei_��zF��#�&.?�G�������9E�AZ���3����g��`��S�Yt<r�g/'<+�D\|�#�����]G�s��{P9�g��U���9��I�����I��m�����)��C�N��(���k6R���F_��j��5kSw����cpD6"��6.~-8������1��c8
���dZ��,��mb��>�����z� C��#)%����+"�D�9nd����+C��w��9����?���?�m��~���������\<cZ��K?U[�����;��	7����^����7Nx��^K����W}�ty�f�4��W��W���,
8����p t�u���)R��U3t��7�$�^N8�67��uL�
�J�&}R��[�h{����9��	�g�F��B�H��R��u���C�N�5;�*��8��;��O��)YG�b���BH�H�&�������'Jy�)�����Dj��8����T?�j>L�V|�����L�
����\��P��`�)��]
E�M�����DD����l����YX�S�����2#���x�q����rv��)��rFH�*�7Us��D]ClL����Ur���9uM���1�28jJ�3x%.�9��{9�X�\��!�=�r ��.{`xpv�No�iSh\Yy3�s��/�,��"@�8������`�sz'�;�E���0����cQ6x9�#�o^�q���t!$����s�RN }�Q��a��c'�e�1���j�!>w���^;Ks�~�5��������qX��t��@#)s�n���W��w��i 88Y� ���,>w������{��2�Z������ke�����.n��g���������|�.������iN�����T$�^�j���9f�j�L��J��Qx�c/���Y��[�{%l�4�t�V-�P���I��)����E����(�fRfle���(�s��#���5m��JeH9���J�?���Pm�P�He&�T`�H8���3
;��sFP9v�NM,�o���|���+>��Hef�>��k�4�F�l pb�*���W�����6pUg27s����q��F�����e�F�t�Q��� ��?�H[#���rz��������"uze��!Yeh�fK�.]�,�
��9�:���Fr���:g���c/'{��0R6�%��[�������=5K���S���}qb�,��.L�=	;�X8�9L��c'��hN�#0��������`�A�2�Nc�s1� �5s.s>9�n}���C��"���a�����?�h�L��������h�F�(a&����Q�zV���;F���d+*��B�p<��p"�D����?��_���������%'��5p��9��������L�2"�+���$/ge���r��I�Y�j��9��T`
o��V��ox�c/��1K�]	�W&�{�1�����T�����q���t�9mO��8�4��R�TP��y���#���U�$�5���q��2���9�o�*�&�|�j�.UH�!K��=���Mr�T�����(\K�����F������*�Nj�'�u��4:)�@HF���7wG8��<����u���1i���:?;�"m���/�9y���8���j���?	��'�>~��YSLg�3,�JOL}�l&������
����~:�:���I�A�]EmiN3��c/����3XN�sR�����)�Ut����a:��gd_Y�"8�T�?ZM��)���G�����&���!���(O^O�v>��$7.;|�\�y���
e�k�'��=r�$�!x���r8yf���	�����dofPL���@?n^����:��\���
}���B��M=+����ck�����6�F��fz����'���I,��7O�,�3�J��"��s�{�S��9��Y��G�ae]��xYs�u�1��%�qn�	�6kH��c/��2��R��B�&��5,#;^����lo(�S��s���}q�a�#)�U6=M-z<��cg�jn��l\���z����&N��l�P
����#��3a*����@z�+}�cg�J��#����8�u�\����Mqi~�)� �b
]�(B�@R���\�sH�#�N_��4�;�hn�:_����f�g
���������-��<�D�O�.���ZX�D��S3^'�v�1���$e�2�S!�{���9j���9���Y��k���7�s7��r����NY���L��O���q�2�r�)Y�wd�_X"$G��F����a�$�NQRT���$�.�$N���1�2����#�t! �I9!��E��s%��s(�#�N"����%@����Lh����^S;�U1:�|�p;�l���]Hr��=p0�[��������)d�Q�G!����O�I,�Ib���>Jbd�V�GS�im���������:���T�=v�0���d��HaL"����|�������c��r�8�P�:w�%�������S�e�rK�T�vTn7X&�[���NALsow1�sN�3����D�$���JC�u<4Y�������`u�������3�l�\��.�����8S�0]�����	)����<1�,��H�t��	�V{�xK�H���<YP���t���Y!B$������`���C������e����I�F���_�n�����s3������6e�C�RL�eX`��	�o�3B
T����|b���	�?��:��I3����9��[7���#VJ��s��#�^N9�����
�d=[�����%�8�r���K�o�s�a{B�����Xd7�"4�(^:b���x�~��"%wqU��{[�FO3��%�Vn&G��B@� �HF�L�~���I��E�A��Ir��5���\\�;�]�X
�{�n�A_F�a�2�Az�|�|����@?'aM&qw�D�y!}��|���"e=m^�#����L���ck�PL�&E��?^1�T�N��\>1���%,���[�I��c��hYs�u�1�dn��9�u<#�li���C�^N9��oH��]�6Y��%�Ie'.�ynx1�sN�s�/��
��)aNPF�X��$����;	VF3����V����:T~�O#�t!���jT����������t'G����P2t-������8�(f��u'eFe3�Bs����>�[�a��;�9"�>�>���1�4	yT
X
T�U}��3��;�tH;,j�����w3�ZC����.&,��Xp���j7�A����B]91���6e�E��{jJ��T{r�L��,��=�z���<w9�r���"dK%��c�}�k`q��N�
�G��9��)�G������q�����9��G����'*
�\x���Q�i���(=)�jT{3]�8�D�K�"�D�Q���9v��%d
<j��@��t���' H�KzR�����f��i�� q�M�:����'�4}N,���zvb�/s	�������/*)o�����7�k�'�Ak����Zf��L���f:#�p��U�rb�b.EK"|�/q�s���c�c��m����R��f��9�r�1G�,&]�^q�e$��#����"����f:���'m_i�j*2���PM����u�P����F�B�������oDj���7��p�C85���s'P��,�P-9�f�z+x�?-=��$p��9��B1�F�j���9s��E��`1�XR���(bI���x���N�=��=�M�t(;��
�����~@�<7�7�k)�Z'��Y�+R'Xv�����e�FJ�UH�a<N��xYs�u������u�V��W>\J?���S~9��T���)5�?3OZ��;�w�X�8+�f:���d_Y�"8�3D]V�W���c�a������	���,f]�@��9�(�� Q����F�0�u!Q���9v����8H�L=�����Y��,5_��t!���3�o�S�����/a���#����������v�\����Yt����-��Fu�n{��D�r�,�%Fx#�E�����,Z�Ft����iJ��MJ��C��!�:����c��8�j�-��t����t��r��rXFss���v���9�.{��
��=�f:����m_�i�j�2���������e����+�P�V����M��I���N��Q��B5L�*�@�l�77���'Qf��V��:���&��I��\�_����v2!O$&�e_�]�����y|3](H�X����L9n��{/���G	J�YP���{�x��{}J;,:i�v�Z"��X�Y�O�j}��0�X�n#�}3�������?�����N
�Jc=5a5�����H=����xt�u�1WH��3�A�l�uV[��c/'�
 ~����6>���(STW������XO�!��"/�s�a{������ S�.�M;2��6���9v�oR��.�D�N)�2�������[�������.��<�D�Dbqx-7�}��_����Xf�E�&�I{&�G�r8�m���G��\,�)j�>�S��I��S���R5Q���iKw�l}��Ug��9wI�"��eE�$�i$e������mS��Y�0���^+��8��+Eo�W�"��f:#��*�a���NL{����<�[�M��g�u�9�:���R�&�j����1�YmCG���r,�@����S��L��i���VC�8��f:����l_�ik$jJZ���^o���f9r�$X![��	��'
e8|$|�>�6����6F�p����P�(>X)�4RTe�w�*�@5AY��s�@��j�i<�C�/��u|$�(������<��@���>�N<��s1�2#"`Y��{�\X�x���N�oG�>5������l�-?��^��t+9wk�4��|�C����P��*^�Xv5����D��<�v���f�
�05�^�K�0;S�c�=�W�M�{���)�,2@�2N�{��5������SbR�Acw��t�1lO��8�2�EO�%LG�B���Nr�,�H$��i�s^���h�oE�p���f�u���8G�`�=�����)��;��Le�LR-�~���I�bq��wMf�
�q��t!���%L��dU�Y)Li�������p��Lf��`�.��t�wY�8���y���6eDC�H�������E"��o�W3#��5��	a�S����vb�JJ���Y2t��([�R�T{r,\B/z��=�K o<��9t���cF���Z*-
����B��gF��E[6�j�?D�b:��6gm_�ik�5J���Y?�Ebto��X!���PD%�(����`xi�����[j��{�\�9�������*]�����{�9
���#W �Z�����ZV,���Y�v�I���8��f�P��@�XL�+z�6�#�Lr�lf-��w5vM���Z���r����3W��d�Qw�����f{���Im�o�,��Xl#m���Lg��R,y�����@4�A6��Bzr&���`t�u�1����g=L�&St�����r��K�Or7���/s,N�����6����FX�9��)��F�����������D�Cv9v���H9���P<���G��X]��7����Cw�9&�����\��)#);�D�2l��4t�#�C��I��8��F����/#�WP���k(���|���>��W)K�c���+B��!�����/"I����t3�Vx�Q�������l��E|k| ���,X�Ev�n>1L)e.��pY2�3&�p���c�c�ip&@��"��%���y���)��� �}���}��,s,��|�{�o
��7�9��=o��L�X�3�� L\�W����cg�jn\� ��1w=V�Ry�3m�Qn�!5� �M$BF��T���r��I�"�K�e�`�kn�L{���|���7�F���t��#!�d����p������1 B%,���z�o���=��=�M��l�I�qj\�n|�G�j��������D�Wr��n;#�P�j�]��'�*�����42��P�O�Em:�j�9�:�����P��:^��0w����)�J�W�`��=&�U.����;=}G8H�:�������}qha����!m���9v�,e1gW�i������A�#�P
�r����v!,�M�P��+�q��,I��cg����39R��3��`�K�'�K�K���������.~?3�k��Qz�<i���c'��Y��Fn�P��Q����^-:�{���)���l����Kg7�V���	��V���b��#f��;7���L����c�y$�� E��#���Y����:�Sb2���H��9�r�1+S^$�S��.$����gJ���������cK��9����Z����l�=��
��\��DK�D�.���h����)e)�?�Z��G�����CX��F������`u�r��I�f�Ro���=TK>���b��T��+S������m�6��B��)���~c��(�A�Id!��vhS2l�����K;����K�h�H�7�f�e���CJQW6�����.,��n��|��d�����^?1{)B�0R.3�<"YGjN��y�:���2���
t�"����S���
��f=^��J�Wy�w�������sb{�����18J���r��{�}����8���iv��y8>�H��q��t(jJcP��:,���{n�"�/w��B1�����D���c,�v.!Ox,K�|��% _�=���b�P|���"2$am���\��gg��m�	�Tr�<'�-�ak?���J�r-����?����:�{���^+���94�I�m�%yH����f;#�i���t�$������Q��C�8o6�������c�N��NQx!����x����9�r�GO�'�R�a��y�Rq
a��.�������X~q��<\-�$q-�G{���s��#���5#�D��R�S.�$ri
���J�b-����B`�������Y����<�w����`UN��c����/�|����T]�eW^����BQ@��H�Y�t�9����e�����#��Ec7Lo��k�f�����`�(�E5�@OM�tHV7�F6c����nb��M��A7�����k}��gY7����t��1��P)��y���tJ+��!�9�:��`yB��)��������S�e����!������#�~{LEM���q��n;� ��f_Z@#���?BR����1��9-�G��D#i��"O����O�H�S���(�!�����a1`1A�.w~��!<�4kH��c��h9��lY2�l!����X&=Y�]z��\��n~�-���G.�H�� +�0�q�?-���89���KhN��5+��kQ����Hz��Dq����7�V���������{��L��HX��.���vFh�U�n��swh����L9~5��#������5�^�C�d���{�%��Z�|���)�L�)W%��g3��E{�S�=�$��8{n�:�l�������S
p�����S��P�<�D9r�$\��~��������2�[K�(��������.V�[5g&E��:dVq�MD���I�j"*�U(�.��tK��=�(���%)q
gh����E��=��p��q���D����(�Q�D�;k-�M�^���N��w=5����`ch���n|�j��q�j�f{-�"�/�ZM������.^'\v9/��&��2e.����;��S�f���!�@�	D-����4R�K{9��a����)�|c��r��{LQ
{|}�s�v�9�=9�����5�x�Y�w�jAf��>r��#�6WR��g�9����E)���z�?������H���rN�QM�E�zo$���HVz��9�4���e���Qd�wIR
�[������F�b�qc\�E����������/��D
T�S�:�5�u|�S�$=k�P7����]��_+J���5��	w��Y�2�EY�����R�O�K��]�'D)�sF��z��M"���9��# �!�U��E6>���{9����t�l�M���������8K�]/�/����f:��6go_�ik%RbPy
��!���>VL`�#k�b���L��4��mP�
�QA���VLC7�@�T�d�z���Y�t�:v������O���F�i��#E)Zt���En�E��0�r�������)9��sQ�#�ad��=sR�!�z����������h�b�e}��LTo4n?���%]�6j]N���1P%]p����QL�:���L	��V�<�������_�X��#)��������=t���c����pE�������)�S�_A���9n�sb{�����4��,�*�n�#~��4��8�3�X�4s����GT�h����g��a�G�0$.��qv{&���w�"`1b���y�E�S,�����8����D}���ab�]�2~��
��z���.�C�N�?��cZ���^7��6K�6E~�)����s�4�w�k�)�E��H��_K��lE�go�3B���]��'&2%rfgI�"��lf����Sj���9V���V9���f����
:�r���E��;�g���U2�u��s%���m�An\���9��9�����J�u�J�{r������
��tf��W<���������X[�8'z�]�y�9����t�W`5�`5#*2D��[^f"�E�|�@%C���(��.|$
��Q`��{�p���C�NF��-AO�m�-���^A����w�P(�m��Y_,Q!�� ����.����m�=��lg��.��|��h��G�(� ��g�����c�c�����#���FS�<:�r�1M.f ��	{^��O_P�Wy�w��i��v�AlO��8�0�FM�L����Yh~a����"T��4J��E����G��d,�����w����c7#i�(.2UgcW�8������7c�;s�Pz��C�����&��Q���5�m���.~����I����??��"���K����2�i�����lI;]��|IO+J����;�e}�N�_*^K�������K�m������4\gLv�/���Yr0���z� ����5�^=C�B7����6���3���v���)��%����Qm������A>�.{��MM)��x�Ym���8�2���w,��G��3K�
��b�-n:%������O�����J�+�n�Zu�F�.9~����Chee��9T�\��v�!O�-��f�]���l5
���
7��B���
��M��[�����Pa�"nt�HX4SI����c����aj.�jG�?5�!e����:���]����,�_��������Q�"�h����g�?��G�f�NL��%QJ�%�P���g�x�L��c�C�Y�(
�Dr���7�������s�XN���L���g�g�[Z&�]�;��?m�j{G������A~ql}��)0)9J��X�S8�Y�G~��#qy�+S�%p�G:�J�8p|D�
�m��l���������2Q��lF�5������ht,�+�Z�?�/�E}n�������[uy���o�a�c<zq��.������I���x��OS	^��D�2��@���4�5O����,!������f���ZEM�M[5��s"QZ���g�dw�F����d>5���,~�R���;�,AM���!��E&�S{j�-_���)��#G��|�E�'r*�]�>O�����n��s�j{������JY(�^oB��Xl��R!��Gx��GN�8\��j���n�\�<\9.���\{M����M�d�c��c��j�,�3��h�6��S�(o�������jj*[�s�\�w����\�)+r&�o������q�r���k;E>"��������0Y1t�(j\Q���K�d��Qn�x�e}�����[����%_�e��"�����vF��:��K����)���9*y��C��9�_j��9���E�W���i�K�z9�W����L��c����������ms�Mr�:n�s�b{�����8���JD��3�)���$�N�Q�8.!f����P���q���r.�?6.)z7^�y�����sTM]`�4����`tM9[�D�L��<��'�M�k������n��} �O���A�:�O��9v���~J)y�Z�@EZ���b�����s��dI���PD�����n}�R�@�>�������dI�M�1����0�J�����C��l"t/�_:2'�5=�����c T8�8�3=���9|��c/�+�\$�$j.=����]r*�]��-	6���f<����p_k�����IR^mN	w��9�Z*\4Qi#�p��B/�K�)�W��:a�w��l+����aF`��YF`���'9v��NQ�%�ZcW�oy���	��o���{�k��?���v�(@cWv�9e����n��}�����l�?$(#����er�{M�m�����K��"�������Cf.7s��n{-�"eZ�yci��1^�]v�����IL�W�;i�9�D���!�R�E��Le��#������c/�����C%j��W�|9�8u�c�T�}�������x�QlO��8�2�GQ��-����i�9I��c'����l
#��3�O��G��hd8k��[n��Q��"���:��]w~�<
���<uE�C��%�'�&Tq�\�z���������.��qg���c�_'��(�P.m�}|��.����������R��$�yKohMc������@�HB�1��e}-k�d[u������vB��T�Lv�����T1[Da��C7��,T�z����;1d��I�|�B<���S�)r�$���1��L��Z	�T��e�7N�}7�sV������IL��zF�`�L�>t�,`1��?��f�Ye������)�*JQ5�Uy7^��F���@M�p.=�G�p��$�N�����T� ���p��[��z����*�a����x�8#q�1�b��R�l���I������dsN��3�=M.sm��O��Q�����:I2E1R�(?��~7��cv�D����/�i3�����xF��*���WOLoJ�)"g��2d:(�$��:�:�X�s��%H�g�2��{9����^r��6Yf5����^�
pk�������D��CKCh,����)sh�{��4	�m��,=;����g��#jV}����p�����CH�� ����q���C�N� {x%,�<�2q�]j3���������B����A`��������w�#�N�_����7J�3.Ju��M;76�6�<7c���(�6i����b�
a'L��r��/�K�6n�'���3XgKv��������,���LO��)]�U�^�JL���9k���������)�4A�tN��r�'���������T��S�rc��n;����q_�j�+�;&s(C���:G�r��Y������B�=-@i��@+��r�!j���v����G��9�$2�����sH�#���5R^W%���=���[Z����X�o��Dz�ej\��n�P��8�
�B���c�r�w��k;��D��E@����0��:��v�����E�H$�q8�����q-9u#�_K�D��ezj�g|��n�J��.����R"��9�E���u�iV�S��u�3��^zc+RO(M���z�r�3��L�T2�yy�|���q*�'%b"���������������
c���
����m�s��<;�Y�t�M��-��>y��lD!��i�����B,"6����� w<\�XA,�$IN\�0]�����@{��w<�����mM��{��<�}n+=����Z����^(T�X��L	����=�P�4*$��Z"C��6��*�{0��o�/�����?���������R���?������C�������_��������_��_��o����?�G�����~��������������������W�?���O��
7���?��?����m��(��������I��]���Pk!
��7F�����f��B������~��������w�^/�u9�x�������~u�|������~�^~��������f��^������)s�i�L5�����Zb�n�z��'����1,f�����3�����mQ��Vc[������m�nU���l{��-*�����m���Vc[4������m�n����l{��-:���j9���mao5���mao5���m�n��b�����b��p�Z�m�f[�[�m�f[�[�m1-�X�b���_�K��a�u*:I:��tuhie�u�k���;��2H;�����FX�ZZ��ZZ��Z*<'�-�"O-U��g��V�g��V����Z���3@K+���_K��D����A����A�����s"���� ���R�9�~hieh����?��f��3�g��^q���_K��L��U�����������:��'�g��V��������3@K+��3@K+���_K��L����A�����s��������������R�9ShieP�k����?��2�?��2�?��T���ZZ���Z�?g��-�����2
���VQ�Y�?���X�Z�?�g�V�+�_K+���_K�g��������������2���2�?��2�?��T��-��O-����3@K+��3@K+���_K�g���������R�Y�?��2�?��2�?��T��-��O-����3@K+��3@k��J�i�UUV���.-V�����J��U�����������R�Y�?��2�?��TV��-���-��O-����3@K+���_K�g��������������R�Y�?��2�?��TV��-���-��O-����3@K+���_K�g��������+�Q�k5����3@�K��a����QhUi�2���2�?��T6��-��O-����3@K+��3@K+���_K�g���������R���?��2�?��2�?��T6��-��O-����3@K+��3@K+���_K�g���������R���?��2�?�������Z]�g�����be�k�����ZUZ�-��O-����3@K+���_K�g��������������R���?��2�?��Tv��-���-��O-����3@K+���_K�g����Z�s��~s�qwq|�������n{����Q���������p���.n��������������������%������PK�D�qg��PK"a�P�l9�..mimetypePK"a�P�u����TThumbnails/thumbnail.pngPK"a�PV*
�]('settings.xmlPK"a�P��h���
manifest.rdfPK"a�P*Configurations2/floater/PK"a�P`Configurations2/menubar/PK"a�P�Configurations2/accelerator/PK"a�P�Configurations2/toolpanel/PK"a�P
Configurations2/images/Bitmaps/PK"a�PE
Configurations2/popupmenu/PK"a�P}
Configurations2/statusbar/PK"a�P�
Configurations2/toolbar/PK"a�P�
Configurations2/progressbar/PK"a�P
�l ��%meta.xmlPK"a�P�|z��w'
styles.xmlPK"a�P�u��1,META-INF/manifest.xmlPK"a�P�D�qg���content.xmlPKe1�
#12Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#10)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, Apr 24, 2020 at 09:22:34PM -0400, James Coleman wrote:

On Fri, Apr 24, 2020 at 5:55 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Fri, Apr 24, 2020 at 09:38:54AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 10:55 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Thu, Apr 23, 2020 at 09:02:26AM -0400, James Coleman wrote:

On Thu, Apr 23, 2020 at 8:47 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 20, 2020 at 09:27:34PM -0400, James Coleman wrote:

Over in "execExprInterp() questions / How to improve scalar array op
expr eval?" [1] I'd mused about how we might be able to optimized
scalar array ops with OR'd semantics.

This patch implements a binary search for such expressions when the
array argument is a constant so that we can avoid needing to teach
expression execution to cache stable values or know when a param has
changed.

The speed-up for the target case can pretty impressive: in my
admittedly contrived and relatively unscientific test with a query in
the form:

select count(*) from generate_series(1,100000) n(i) where i in (<1000
random integers in the series>)

shows ~30ms for the patch versus ~640ms on master.

Nice improvement, although 1000 items is probably a bit unusual. The
threshold used in the patch (9 elements) seems a bit too low - what
results have you seen with smaller arrays?

At least in our systems we regularly work with 1000 batches of items,
which means you get IN clauses of identifiers of that size. Admittedly
the most common case sees those IN clauses as simple index scans
(e.g., WHERE <primary key> IN (...)), but it's also common to have a
broader query that merely filters additionally on something like "...
AND <some foreign key> IN (...)" where it makes sense for the rest of
the quals to take precedence in generating a reasonable plan. In that
case, the IN becomes a regular filter, hence the idea behind the
patch.

Side note: I'd love for us to be able to treat "IN (VALUES)" the same
way...but as noted in the other thread that's an extremely large
amount of work, I think. But similarly you could use a hash here
instead of a binary search...but this seems quite good.

As to the choice of 9 elements: I just picked that as a starting
point; Andres had previously commented off hand that at 8 elements
serial scanning was faster, so I figured this was a reasonable
starting point for discussion.

Perhaps it would make sense to determine that minimum not as a pure
constant but scaled based on how many rows the planner expects us to
see? Of course that'd be a more invasive patch...so may or may not be
as feasible as a reasonable default.

Not sure. That seems a bit overcomplicated and I don't think it depends
on the number of rows the planner expects to see very much. I think we
usually assume the linear search is cheaper for small arrays and then at
some point the binary search starts winning The question is where this
"break even" point is.

Well since it has to do preprocessing work (expanding the array and
then sorting it), then the number of rows processed matters, right?
For example, doing a linear search on 1000 items only once is going to
be cheaper than preprocessing the array and then doing a binary
search, but only a very large row count the binary search +
preprocessing might very well win out for only a 10 element array.

Hmmm, good point. Essentially the initialization (sorting of the array)
has some cost, and the question is how much extra per-tuple cost this
adds. It's probably not worth it for a single lookup, but for many
lookups it's probably OK. Let's see if I can do the math right:

N - number of lookups
K - number of array elements

Cost to sort the array is

O(K * log(K)) = C1 * K * log(K)

and the cost of a lookup is C2 * log(K), so with the extra cost amortized
for N lookups, the total "per lookup" cost is

C1 * K * log(K) / N + C2 * log(K) = log(K) * (C1 * K / N + C2)

We need to compare this to the O(K) cost of simple linear search, and
the question is at which point the linear search gets more expensive:

C3 * K = log(K) * (C1 * K / N + C2)

I think we can assume that C3 is somewhere in between 0.5 and 1, i.e. if
there's a matching item we find it half-way through on average, and if
there is not we have to walk the whole array. So let's say it's 1.

C1 and C2 are probably fairly low, I think - C1 is typically ~1.4 for
random pivot choice IIRC, and C2 is probably similar. With both values
being ~1.5 we get this:

K = log(K) * (1.5 * K/N + 1.5)

for a fixed K, we get this formula for N:

N = log(K) * 1.5 * K / (K - 1.5 * log(K))

and for a bunch of K values the results look like this:

K | N
-------|-------
1 | 0
10 | 5.27
100 | 7.42
1000 | 10.47
10000 | 13.83
100000 | 17.27

i.e. the binary search with 10k values starts winning over linear search
with just ~13 lookups.

(Assuming I haven't made some silly mistake in the math ...)

Obviously, this only accounts for cost of comparisons and neglects e.g.
the indirect costs for less predictable memory access patterns mentioned
by Andres in his response.

But I think it still shows the number of lookups needed for the binary
search to be a win is pretty low - at least for reasonable number of
values in the array. Maybe it's 20 and not 10, but I don't think that
changes much.

The other question is if we can get N at all and how reliable the value
is. We can probably get the number of rows, but that will ignore other
conditions that may eliminate the row before the binary search.

I'm not trying to argue for more work for myself here: I think the
optimization is worth it on its own, and something like this could be
a further improvement on its own. But it is interesting to think
about.

I don't know. Clearly, if the user sends a query with 10k values and
only does a single lookup, that won't win. And if we can reasonably and
reliably protect against that, I wouldn't mind doing that, although it
means a risk of not using the bin search in case of underestimates etc.

I don't have any hard data about this, but I think we can assume the
number of rows processed by the clause is (much) higher than the number
of keys in it. If you have a clause with 10k values, then you probably
expect it to be applied to many rows, far more than the "beak even"
point of about 10-20 rows ...

So I wouldn't worry about this too much.

Yeah. I think it becomes a lot more interesting in the future if/when
we end up with a way to use this with params and not just constant
arrays. Then the "group" size would matter a whole lot more.

True. That probably changes the calculations quite a bit.

For now, the constant amount of overhead is quite small, so even if we
only execute it once we won't make the query that much worse (or, at
least, the total query time will still be very small). Also, because
it's only applied to constants, there's a natural limit to how much
overhead we're likely to introduce into a query.

FWIW the results from repeated test with both int and text columns that
I shared in [1]/messages/by-id/20200425124024.hsv7z6bia752uymz@development also have data for smaller numbers of rows. I haven't
tried very much to minimize noise (the original goal was to test speedup
for large numbers of rows and large arrays, where this is not an issue).
But I think it still shows that the threshold of ~10 elements is in the
right ballpark. We might use a higher value to be a bit more defensive,
but it's never going to be perfect for types with both cheap and more
expensive comparisons.

One more note - shouldn't this also tweak cost_qual_eval_walker which
computes cost for ScalarArrayOpExpr? At the moment it does this:

/*
* Estimate that the operator will be applied to about half of the
* array elements before the answer is determined.
*/

but that's appropriate for linear search.

[1]: /messages/by-id/20200425124024.hsv7z6bia752uymz@development

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#13David Rowley
dgrowleyml@gmail.com
In reply to: Tomas Vondra (#11)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

David

#14James Coleman
jtc331@gmail.com
In reply to: David Rowley (#13)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]/messages/by-id/CAAaqYe_zVVOURfdPbAhssijw7yV0uKi350gQ=_QGDz7R=HpGGQ@mail.gmail.com. I think it'd be a very
significant effort though.

James

[1]: /messages/by-id/CAAaqYe_zVVOURfdPbAhssijw7yV0uKi350gQ=_QGDz7R=HpGGQ@mail.gmail.com

#15Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#14)
2 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

test-saop-hashjoin.shapplication/x-shDownload
saop-hashjoin.odsapplication/vnd.oasis.opendocument.spreadsheetDownload
PK��P�l9�..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK��P���[ppThumbnails/thumbnail.png�PNG


IHDR����'efPLTE""",,,333;;;CCCLLLTTTZZZccckkksss{{{����������������������������������������������������j�6�IDATx���
s�8`L���o�H���/����������}��r\
<QZt��iLM;��hu�����*�7MC5��.~��?�NoUW�h��_2�[�����T�g��;C�w���_2%�}�'��g��������=�k��l5.+��^(�@�s/��t�����W�{��rM����;��=�n��FA_�7�^���]nB9���Q���sCG�`������/}�f�����Fb�]
���,VR��
��3��n��������#���dI�m�;�K�[�a���=�������Ur���$����AJ+7�/��|����[����b��������"���~�����Q[j���0j+�]�ssv9�������7��������W�����Y��Cn��=�����������fw��y�����	���Z��N�����P
u�{�M��z���Q����;���h��Zek���t���3c��`|w��	���Ft��R�:Ar��z/#�l��zF*�[ClG�^.��Kl�I�.���@�}���P����a)����/%����m���l�������u�qI�E'{5=�Z�a��V�g��Wq�M���E|w*�(����X�n������w���Y9��Oc|s���<���a�G�'����P��>�J?��+~�������X1j��'����C=��C=��C=��C=��C=��f}s9W���&�r�v����SE��&z���WgND�rqW��Le����I	��:���C�o��(�QF��$��Np�����U����������U+��V��y�"���]�c��������e�;�����
Q�G����C=��C=��C=��C=��C=���_���Jf5�}g�����Y$/U��{A}0�p���L/���B�4�=���z��������{��6b�V�`��4gJe9�z���z���z���z���z���~�>�4a���}���zmM;�����P��-�������c+�?��<������c~�����z/�|��V������n�[��}��#�M}���_�/�M�[�����
��\���K��i�b����46o[���4�5�Z���z���z���z���z���z����������v>�^O�4�>���`��n��_u��p
���^s�R~��b�n��R�uWIvL��
U��+TQk��z���z���z���z���z��P�]E�g������^92���'�)Y�������������$]G"��3���c����j���G�����{��1+����>�T�������W{��/���z���z���z���z�������&uU��O_�?�[M;����7d�3}
T����#���~��}<L�kN�wN
��Q{y~��,2��?���(]
����?���c���|�����@�{�ug����{�\�������e{��U'-��m��)������
�v���C=��C=���1��]��cVn�IEND�B`�PK��Psettings.xml�Z[s�8~�_��u'��K��1�[$����l��Y�H2����
dR�Sj�nw'y���s|�������KN�f�&W���N����tv��W�/�����#���0�w��SR�)�D-��������X�)rA��]f�������H����`�|��K���� >g����R)�n���>��������b��

����5�<����l�|c�b������+���S,�
ms���v�S"����r��}�f�����y���\yjS��h��A~	��2�1v�|t��V:;�x6��zA��*�*��E�)�,������Q��CT�����)$W�UBF~I�tG�R&����/���S�k>��3����d��K�����)��8~aT"bz�.s`��s�� 8p����w��(�
�V��u[�D������o�M�ima�8�r$n��{�ZeR27E�)c�@����t�������Im�f:�w�/��s49���c�U$�!����2&8X�;�U<t`��U��GDD��=7��Kq�@*m����? >��# $	�����GH�?������8t'G�FL9�l|���9#�B<�|,\]\2}�9�����*���9�Uh�"f
�E����N��c2+�l�{�Zc���t)^���)����BBi���`A�<!�@*7�z[���`
���&���T�D�o����	�r�uz�X:������0		��JC�X=������"W|iQ�������//��7}��,b�O	|��I�����X�+��pF�K<K��I�����yx}��(d������%Z�Q�d���4$a�[w-P���,U�=�P�T.4��h�b:����i���a�9G<�a�1T�d�$��A��S�C��V:B�N���W��_�UC����z���*I�c�=���
8��{�S[�hO�#
������k�'9��y�^{d�M��������Y�0'�8��.bj�,]��O�������C��v��8�����G?��6O4!E���A�	�������VoP�?__����'F�S����&y��<�o������G���idZ���2Ey��u�����i��[3����/Y
��&��d&H���B.���"	+d��;�-	4s����~����_�0�_��t2����3l��/z�g�X�����^�u}n����g�%l4���/k��B�m:i����5n���P�>u���-l��s�I�S�o�}�l��9��d�U���[�����R<Lr?���V������g������,(�~�[���.�z�F��n�~���./VsT��������t2�����h�����@j��7�����@=���m�l�����;����^m�}�����f���ec�(���h���3�
e��U4�������t���������#xXC�%{���E�&�Ee�T�1���ENPc�6w�?�j3�{��?PK���{�+PK��Pmanifest.rdf���n�0��<�e��@/r(��j��5�X/������VQ�������F3�����a�����T4c)%�Hh��+:�.���:���+��j���*�wn*9_��-7l���(x��<O�"��8qH���	�Bi��|9��	fWQt���y� =��:���
a�R��� ��@�	L��t��NK�3��Q9�����`����<`�+�������^����\��|�hz�czu����#�`�2�O��;y���.�����vDl@��g�����UG�PK��h��PK��PConfigurations2/images/Bitmaps/PK��PConfigurations2/popupmenu/PK��PConfigurations2/statusbar/PK��PConfigurations2/accelerator/PK��PConfigurations2/progressbar/PK��PConfigurations2/toolbar/PK��PConfigurations2/toolpanel/PK��PConfigurations2/floater/PK��PConfigurations2/menubar/PK��Pmeta.xml�����0��}
���������j��Uw�c{�[cG�L��/HI�����{�(����^���5H�D�p+�i*�����S���//�K*,�[i<l�g��j::�*�;C-�TG
keG=��,��B�4
F�r�����w�����_����EQ�P]P�o��w:P�#�����4N���	�7���#YkoF#>�v8I���8!��6CCB�|U��D��W�A����.Ce�WLC�$�����.�j�p�D���V�h8��'8�	�xwH0>��Ip^R�s�;J��V�#��8'$LK�`#�rU��I16
����C��?�9�)�w�_\��N��gybi�������p�h�x��ge����~w��h���������WZ@<���7��=��;�xt�NZBn{�+��$r��M��e{��BP]���G[�Z�PK��l���PK��P
styles.xml�Zmo�6��_a����%�Ik��m]�mP�����%J&J�E���~GJ��7Gq���S41�������/��	�1�	K/����F8
XH����t{m�[W��.X��!V	N���;����_L^Z+���$�S�����2�j!�D�j�bD)*�����[1TXbk�h1|e6�C�6C�%85�#6Tx�S;bv��	��bKI���Z
�����l��������f3G�V.[q�Pa�`��b�3O�M�@C��X��t�,0L
���� ����a�L�Z~������{h���3���4�*���M�X����y���wowy���kIl����l����g�U�J��������8�{���p"07��^x�hP1��.�7qa��Ly����^�����2$����*��Hh��Y
�yvB���eEc�	��������2��^���HLU@������"�J���
2�6���)D��_�`fe�,�CC��Lu�U.u�a�Nr���d,�
�Z���Tt����#�l��@�,�16S����3b�kF(�v���/��W
�����K�-����}D)$)�%
M����G�n��AkTS-�v�S��'��<�!2"h/k���
g�io8#�Zm��Q�b�5�=���\�����F�.P�e���d6:}�/�����%�Z��x�5�����L����(�([��/������4ha��\�T��g���&��-���I�����
�0�S5@�
�}�h5U��Q�
���4��R�����o7m�r��x����%*��4����Q1����8��g�Z�
�n\������\Hb"rhj��:>���X�y�����<��U9U����}����28��r�K��
TN���2o7V�S8z ��`/��.
�(�CJ��g��<�.@�g��5�hr�����}��?��r�����{/��wD�o��G%��z�a�[U�UI4X�M0���ck#x�.�(f)�����KnR��0]�I�������_C��rF�/�:w�O�7�8U�V�#��1�>
��~������OP�w`Gq�>�c��0~P���~�F�1*cz �{(Z	���3�P
?�U~����`,<Hwi�!5��������u��c��
^	�K��"�l|CH�����_��g��c !A�����O=I6I����s���#(?��:R�GQ����/A������n��1�/%i��B��3��C�cG[�U������#h{���Ei���m�����5���d���r���|R����rT�=�)�m�oH�����
k"��:�72�qj�l�����l�r�3��%�L!��7;�AHwJ��.�	��������`���i<�,���E�	l=����s�J��x�%���_~�';a!�Qn����%Fa��[9��~�i���e/�&c���	�1LQ��[��1�`B��^��YfF�����0S�����=N+
�����g>~��'����|����b�;>�h��3�+*vI��V��<�d�����Y��v�r"Ay���a�������YM�0~�������%�z~uuu�4���AB#�2������
n���K_�7���3��D�g��L��j��5�i�x��O��0����rLX��%�p�?	"�	-���"��bmHUR���-�)�5s����w��r��s=�vOl��������ni��i���UK�V�z�d�����{z�zvr>�K��_�tW��{���sL���|63��#����=������PK` ��'PK��PMETA-INF/manifest.xml�S��� ��)��%���"M{(�t��c*�Qt,���	M�e)4��:��7���{W� &��a��U�����}��O���6�Bk ��U����mX�(�J6IT=$I��P�6��$�������	X�+��p�pc'' �3jE��J��%��7�� g#�vU�-��.��r`�suPtl�x��h�j�h�
��v$N����[�)DP:��%R�����.	��<`�@��Ub�/bI@T��xI�2� �P^<���������{��v9�i-��R�8����v��� �zyj�.^?�b�����F����_PK�u��1,PK��Pcontent.xml��a�7�%��~E�������p]����M�k3kk�skg��6V"KwI������H)�2�Q�dh4����3���s���������������~����O7^��y��������_����_��?������7���}���7^������S���������_�>|{}������o��x������~��/��?�V�����������?�p����N������_�`��O��������m|�_�����?���w/~��O����������������������/��7����_^���<|����?�}��������/o����	^~�}�p]�_����}���]���~�>�T?���G��vS`���/����_���o���_�����`��P�7��Bo������>���_�7_��_~�������l���^���X�6���?{{����y��Ek����_X��U����>��}a�����������o��zh�^F�7K!�y�q_�|������M�W���/����7��7~�������?��d���P|��������������7~Z���_��+o���L�{���it�^�m$.�{{��?�ao�z<�������W��d�yI���������������&o�a����������w������~w��+�3����������O���[:�kno_���������_��������u)�o����7>Z�������g�o����������J_���_>�y/���/~�9����_��xs����_�����������������8<�?���q������������w���������]������_��_�r�!>��]�������>�����������]�Ow�o�^�O���o���?Xt��u�����y����|���O���w���l��e���������H{�����|���?�������&��E<�������>�����c������7�������n�����nb��_�^��o��{�&��������p���]�����9��K����_�z���_��4!����������o�_�{��O?���������~���1��{�����S\��|��w���o(��o��|w��������;f�/������������]�������N�V|�c���w�-���+F����W}��x�����/N|s�����-�������}�����������?������������)����w�����.���LO�����������K���s:�|�%�#���M|K?�><������}�?��?�}a����Cx����]��~�����Qu|��ez`������x�O���/��Y~��ps���z(��{���<���+?����/[n��V����k�<��g]-�����4�_������������1���s2�E��u��x2�y �k-��f�<D/u�Ydo^*�y��sz5�p5����n,����]���M����5o�f��"��yv����:�. <uX�=�3����Y���GK>��`:����������zR��Y{��y�������	���[��m"��o�K���-����R'�y>��%��1�����;�K�-�VZ���9�S����-u=����r���d��h��
Z��y��
{�u�k��{���,�������[�����
:��o,f?���0������6}|K���|�#������7�5�x�>���[<������_���,�y�g��%��-	a�K��Z�����$�m/��8
#S���$�e�h�Z[�<�ml�oIK%�������?������_���o�0|������w������_0��y�em���>��g���TT�#��[r�z)��l_<��{��`[��`G����9��*c��G����i�9,�e�#/��l_�[y��,
.N�����?~���xws������������A���j�/�o���'�����o?�y�����w�?|�������&	��������'q�$z���������{s���a����d�����~����d
��C��	���d��7�A�`��M*{���<�dz����x����;W��a���'_���:����RX�.����%!��/_����/�dA��)�e��u�c������wq�}��O��'�_ }��_������~��ek����|w�:mC8?��!y��>����p���w��HuD3����2�����������_�{���O�no�/��������������y�������Wq_\���o#�sl������������X��?���~��w~�.�!�r?���=��nY�n�*K�l#����J����5=9ok���8�����[Y�]4]`'l{v��k����p�Y�������
����a���I7��?Z��2p`u�H,������X�l@����x����wo5p��N[|�������i�H�%��z����u�?N�r_���j����x{�l1x���������y
�T����r������G���. 9
BM���=�����9�tm��D��f@;v�M7h����APA���}:����x�x�������/����l�+Uz��~���E��O�h�@�z������W���m�pc9��i����ye���v0���|4]+�VoB�t�M�4cTr��'�/������>�Cy>@y���E�w|���Ri�e��������x��������������y��P������<Z��y���T�oF����Xr��(��o��k�=�!�vq�|=r��!�����y+���%{:���8�����F��Q��K�-�f����\4��+����Z"�J���.cE\���Z�i_~4��.�����`G�S{*'��a/c���������?�;k4(C<����`
Fy����j�>��A��"P|�&�"�w��L���u��������t8�p�D6���%�
�����h���&4�; �Ak3�d�]HcH;����xi�(�;��4�}2W�M�b�~��rZ������f,+ ����rM��302�~6��X�<��<���u-Ww�`�	�F���#��N�����������}������ q��8]Kr�bp�����U��`"�p�3����:C�Op�u�����e�]hbS�=�e�c�1	�r��"��vx���:���>�M�XF�CCC��#��G��"�g��3y���D�����|Y�����1B���d7���*xAPC	�s�Fe�5���������%�$l���t��������k��P�U����k�_���E�E�o��7�c_�h�'
��rj��xd-���cv������ ��R��i0����|{��FQ�G$��������][���` [���v�Ag�?&�(�������uh�aH��6���f!��?��Y�N"n
i6n��K���q��V�c&���2B�����S|"���P.d�\�=0]��R���s�t]��������R�`���������������t	����Y�:E<������e�Cy�F�v������i�G�l��"<Ei�eZ�gx��#��DM]��>�9����EM%�x�G����/;�!H�Y.|��;e|�q�N�����v����#����J��|G�����%�~�w�B66��eM��~�����s����������|x"�U�h�.�N;��r����Xl9r/�������A��;B�Y��U.R!�wx`�#�(	���������t��B�v����h��`�A�-EK��E	��{O@aG>�#��z|����P><n��w�}W��`"�_}�y�	�mi�@�����������`K���t�iaF
,6M�h	i1�W��W�'?�L���E�Fg�v�_���6��*N/�$�|�c/o_�m�������7T��'��9�t�V�^2]cQ��x����1:���F�h�> ���-=��qc�ga���_�{�`��tEy���5/U-���5��cL���-��O���A�Fy{��?�n!�W�Z`�$*]����{���p��HGr���������,�T�����b�/o�h���z�P�v�,6&���c�b[/N�.�c��X`8�b����8vh�v��d�.uLk�|6]���k�K���c4XGe�E�qV�1+�R�aSia�
/���c�������I�����I�C�H�w{o�@��XQ�u��>���Q�W�wm4�Qmd������v���/?�$���K�bE��\��8y���J����v�3�*	��B�a����#�7�7���{z��x��e�����6��������_����B��Ye�4��T��xUM����������tef9&�?�������aI����d�|�Bd���e�T���c*u��d���WJw��kw>�g����G��8�g���������b��R�`p�"{�����S��k���3Zn1���U^�0����FV�3��A+��l�Y$;��^H�c$�`c�/{?�������6�R_'�n����'�1��W]�����F>����H�-��3�p(��l����2�l���=�/��}��m��K6]�K���s/�.�]Ksbi�sX]�0m#�p�?f����Ojs!.�4���;m��+@��C|�`Q�����`��gVc�p�)w��.��;�=��_��6�)���8��o��U�|e��B�u�4	E��k�yHb�BX��oZN+��l:x����4�m@q�%g!{���V=
�RWd�V�M�uOL�q��w�t}��W��L�k�c�\����"�8����1`t��
��������2�����v��~�R�jLf4=M��k�Z���[��JQS��(�P���v������IeuS6�EH��C�������g���
w`��������Er�?@��"��J��$M�8��1<���T�B�M�����cR�� �$�[Ry�[���l_6@n �#1�W.�O��R�i�h��l6]�'Z+��'�uAr0J���_2]$��>�;�,��40;j�@C�����W���
��iaAZ���e&nu{o��`t�9?�������X\(�w/T9k%�n�2<��@Do���2@���7
�����PW]�L�i������c��
�!�D�8�y�kH���d�=����p���DU��e��m��:������kK�u�j�����xe���U��l�.�W��;O���h��3��	f�E�t*�=Be6����e�@-�QL��V�1���~���,����BssO	J�z�'���_h�*So�\��`�������\+��m��������DlK��%�#w�
�����P��>�h�]$��H�R�5"s��%;�]�7�7��N�D	8�|�v�,�IK�IS��r���%\��_�r��������
����2�j�>>Y��;�G�R
����h*�����e�,Wj�f�E�r�%�=�d����Tm��!]���n��_��@�q�N���7���:Khy��,��ZT[,V(I<�7��/.�x�t�^[c�m����`������h��Q��1�J}*�)&��b�mL{	�&����HW,�,(G�����3��F!W.����8�a�Q�h�K"��*�nP��w������~�����������~x���>1��l����
Y������l��{��U�t]�;������������J-�t	�Y��;T%l��V��=������u���+�����lM�E�&��=(~����v��Z�M-KI�������;��y�E]�Q�M��9���s	���7��s�c��"+)�l�]$�[p:8��+��n��r�����w��+�.�����X7��/�VK�]i�������$>����[�xk\��i������(��p���_.��<�������a�V�MA�Y!�;D/���O_�Xl1�L�w����{������a	�9�{���E����t���f5���d���A���������gF�'�QI"f���8�o����C!ZcI�!2j
�%����~�+�WE4�����Ep�K���c}�%��������{��t��U)A�]����5���Z9�l�6fW��M�������K�ER3���M�m����^���j�>in�
���/M������9����>ij��'����Kc��v�B6�A����#=|i�[�����f�}�M�d���	6i�do|���QI�%�i�Nk���I��cg�������i���'����TR|���in�\�#G��i�P�6�s����MW����
4�.��2����``QI��I�MX����_�x�l��1'�_�hia��[
��m�J�[��v�5in����%����I���������o��#�BY��(��?����x.����R�����3���x�����M@���:T#X��;u-4X5z!"�;���[tS�i���tm�)���l�6�j'�e���������2�.���t��%����Q��Dx�H�����v����4�����Z�������������
x�����;��1��C�J���vB�]�f�#�Hh*
d�#�C#����{���f����TF.i��N��j��%��(����+'6{����F�������
�������8vF��?��`�.m�\�x6]�^��R#��{�t]d�CeM@6]��m�E*��-�qR,��+�a)����U��)���tl�6�5<��]�����j��Y���x�����P�8n>X9E8�n-��q�'�^��e�K��`dl14H�0��v���t��v�!�����B�8���G�/Ok�:Ut�������YW���n:�R]eqa����|������K��Q+i%��M�vl�����d�.Iq�����>�y�G[)k�M��Y-4�]X#��5~�%�r�
W����g��������S/��m��HK�����*�!����'�t�]�f
R���L��E����y7�������e���E2�J��l�]$��HD����X������������u
�Ct���\���!�KC���'WE�	mcN8���+��]kc�Ce���ru�[
�-�>���9s���l�6�%_J	�^4]�jV@��
0��H�K���y�2�7{��}J_,�m��j���Qy���}�"��t�He���^
����H2T�%����B��V��S/F�X`�{a��q�\q���������q	�&up���% ���Z9�'�n���f��\�����J�@k6���������Jd5�����R)�M����8[9�#��������C�M��YbL�U#lj$���AxO�+��������T� 5"s�������XX��W��[#����C��������1.�7�{NIK�xw"�[��E"�bc���@`��C��
�����`�]��8���d%xl!�c�#���j�&������@c�_-^�Rb��k����r)���s[
�u�������8_9 ��L,�T�Uz�teb�������"9�i���E���cB_��<�-��p���U��I��'���5u���] �������79�w|����G�ET;kz��dh��a�W���Z&�Y1~dU�����A�R���m7��w�Gu��zu�x_�x���������t�A�9�l���`�m1���������c��?cSu����'�(-v����Of��A6j�������j����2����Cf��y6]�Gy���f���Q"�gp�-�E��_Ff|�����$C�=dgE�������,�!���� -��S%c����P���j\m^6�$Q���W�6D0_��<�$O�El��
��W�e�����XN������5!�zl�.D������N��L��,)�-�d:X�3���P���-�{�F��{�G���*��Mb�]��4L�.����_(�-�{9�PH�k��������tP�(�Pa��b���
NKK4OT(��<���������65������EP<�+���M�bpo���pV%�]e��d�!��� &�JQ@mJ���7vx<@X��w�x�u��>�V�^B���tX��;R���/��Z��l:�������ir���R)��j��_?3$N�X%���x]������M�e�!��JY����`Z
q]��r(
Y�,��Q]����!-������������?�i,�z�J�,.R�j����02P�o��t�����5'�!x��T���v8`��s>.=N�)��;����:4|^;���b5h�>�P,��X��9�� �M��������a��Z��jmSa�,��r��;H��[ZS=���������6'���g�u�gP�R�l�]�#���:Q�����9b1�-����f7������������n��j��������N}>��u���j�"l�v?&���-����B��'�?�q�4�%b�tL��?c�s����&V2���j�D5��*��v���Q1�� Xl����|�<*2�,�9�Se��o{0R���&�
����t2�����h:����`y�����$���m%��Wf�cZW,^z����$4EQ{)q0]QS�fk�_�]S�	S����t������Z
SO�w�o� �a��i�!�Q���i6*�'���Ot����,.������~��u���%
�����`z�9;6:UT{�>�n�hgD���Y��e�a4�V�lA��&�=(��LR�Y�P���a��o���L������-�RgB�����~9�~��}~���^������+�9�k�����8)�	=r.��K�GD^�0�M��]Hl]��r]�_�40��9\F�'}2�����4�:�Cq{�J^J����>�	��:�1�j���Q(��?��Am�����}�W�^�i�8U��l�!��1L���� �4���
-�44x�2�+�;���A�fu<Q�x�`��3:rH�
�K��-�a�.G6�������H�tR����2��%H�����:�T ����g[W�}�\����mmr-W���Y����Ig
{t����df'Rz���E(�'�����]O��J���������x�
�Phr�>���]#� ��������G�#������p��vw�����!��lki�d�a,���0�ZaFZz]#����.��}e�=�E���~����]g����X�sk!_����>4���Sw��T4]����F��E���:�r�c,L�ca��9��t4L�Tuh^X��F�:��T�����i�*��_�RL�]�&��u}��?� 
>@�M��R��>��k�Y��z��7��	��A�"��l�m
���c���u���8���^(�qFP��i�cMJ� ������U����_��M��Ds�RM��,�9���:��%�i��
�:%�����R���MW'��rw6]�W��,������s8�wL�a'Vg�"u�?N�_u�m|`�[�Ms��6$E�?����}�2!&�Ms��������.���Y��
����A\�
q� j��b������R&�n�<#���q!.MIj�I��#Wu�L��L8�zn��q;K�t��.\��d=K��h��aG��w&|&<�N���k3�R�q�]�\�����tm(\=��v�/?��Vuh����#
�@�����������_���	cf��Z���y/E0������������T��� .�	/]/�U������3�_L����
����j��7h���I�����Am�c�����k+O������xw���*,���E�f,Nw�#C�(�8�oP�|`�{`2�bXj�y�\6]�=0������1������d�.��jE7�Ut;��nMs���9��c��r?�����T�����S�7l
��#��"C�_��?b$�_h�Gzr|�����"��P�hF6�e���g�8���m���c���������i�t�Xv��lC���4!����0����n|!��:�v~��t(�g�(����zijQ���=���K����r�zR��V6]�#O����=��{��������kc��8�X�l�����\�>Fb�0BLk9���%��P�T���?��(��	%�oN��(��N�%��v���q'O�R�.�P�<�qW����`!�$3b�T�����)����;C��
��v�h6�0����v�l1�Qj����rZ���o#}��J��l:����1i��u$��~B�T���������$����{W���M����`���*G���r�8jw�d�R����oV�����%s-�
�����BH�O�����&XHL�U	M��q�/�����D����!C���7��j��w�s�h�*'!d������������][�-�>��\�����g�t�X�9�������-�p��p!������b�-S��G�@����Z�>��QD[n���e�OP��
�(e���������2EtY[��k���	�
d�t]��F�z�L����I��wh����;�\h�0��/"�����U��iye12`h��
�����?���Z��$H��Mpv�����F���`�.������X��#q��o�a06�.o�P*<:�h�aPsP[�t�@���t����}����p����d<�����LG��2c�b/�!�t��Lj��Y8�R.>0���9^���u1��*���|������+3�_�g�m���������i0
�09N-h
��b���TD��?��	��i���7��0��g���1W.�����P�l�����A�E9�q�B��tC��3V�%@"���f����F��C��T)Z�M��e�sb���s��O�XZ�xo���V�+K%� j��	}
,b)a/�������
�
�zo��/O*3�R��]�1����R����%���y����@?1��:u�v����w�������gPl�q�X�v�q�����C�=!#��,�S����;���w����^B�R� jtg�����V�x�U����$����**IW��748r������F��U�u��0�]Xs<ZQ<-@��G*�8�w���:�cRQw�,G@��y8RF�=[�&�%�o4���.���}9��l�J�����veL�n(�'#�d�.7b��:�mW�X$��As�]4O�e��]F�]�����t#qj�#o�	����{*	l�?�������-����|]�B�3�w��<8������O�����,z��(�r���
�^o=�Ut�4k�?���MC�������Rf$�n�a�V,���1�^����h<
G���7� �X�WvaL�#�f�_1$������T�*��c��|��b����f�U���L�QQy6^�k4��"��velU��e���s���3���@g����C��21!;�`IZ��x�6�'��:�O��.��R�v�:�{������,�������.4)B���:����9�W��M�����(���bp�T���#�N8����51������a�3��;�g"zm�7Go\c����������q@uf�m@�B3���6B�+���/��ho�����c���E���Wf�m�`��v]R�X����l�E����K�l�A����tpY��1n�����XYO�v���!M�|�PQf���(=) V���Y��
l9U:�&�(k�����9�ieg���y��vC����c�%�!/7um#4���v���B��
c���2:�A��]%>p,c(z3�pg*��&����H]#�euh�H��y������k������s)�$�x�L��z]Z5���}��u�=���}��d�.tN�6j��'�e���v&=jg��,E��������-�.6�Z�*��7���h�#�-����x;��_-b��A�f��%�����5.��&4�WvxL���bHu��V������ ����5���2S�l��`g�u�b�Yv>p��6O�X4��8k������YiC��)b�X�_:������$�ay[����������_N���{����������]0�+���.o��M��kw�d��{�J��G�%�uI����u�c6]���t�5x�tT&i���O�����6�9c�D���T
���5���}��!���E���5��x���������Ay>������\y��M7�<4c���m`D4n�E�����\96$�n�nF,K`K��m��U��K�4�w������I}
(#��?#���>�����	#���&W>�����[���|��P�+�.�[8��������_4���l�:������t$?������5���Z�0�vb�x�L�]��X�l����bh�qd���?N-V-���i�s�Y���k����h�!�1�����2� �Az����~����o,Zn�a����@�o�YK�����/��oa�*�.��^g,�B�`B�=-�]�g�r	~h��KeA{6]��Z��l�6��T9-����+U4��"HY'y5���������oR��d�B��:�v��';Vv����OS�4����?�c��:��4�+>�D���@�K����M@��
37�m����������x�Hvu���F2��dA
i���oc�d�at;�=H��!����t�4')c��[�M9
�]�2�e��Dx��5��N�K}��,Q3�v�����r�QF����+��'�������vuX������_F#�����.�!��9����$4B��S��?:N|�+�j�Z!��������2U*kd�
����a���,�����-C��������+����A�s��et�	��sG������b�����9���e��QB�A�@x#;��5�i��L�k�'������DxpR�^2])V_�$�e��$���n@�MlVj�����<	q����U��)KWE
^��C��Bb������z��xtr�h��'���Q\�����e��3��#ri������A��vht�~$S��t2�,��������ZJBu���}6�8�x;�;�
f��ArL�f������v�������}2�8�����ugO6]�QC>T�>����8��d���d0�}��%02�,���liD�I�����(fE�`�����FN���V���J?5�H���Y���Lc~&I7�M�N���8`q�lr�j1F6�c��5�SU ��������������1R��q�t��v3�Z�Kc��Mzb���y/!M=.��������9-�Pe�M��70�i�|���N������+�zl�����v/J��R�	3���~�k�6��������2��'����q�5,+�L�j���{q C�h�M�_u�����F�A�-�*�N������?��8n�&��d)�����b�������tCx$3���NEcM�b�>��i74�_?�m��U2�0���]8aeg)H�2'j���Q)����W�����v��@]g��(�8j�N(���X{�x�kmD8��g���po��'�������e�����?=�|zGx�rZ����A�9�h���x2,S0>���L�!
���&1X����W��?��C<99�����[��B\,J��v�h�������t�"@n�0v����x�������t���Am�y+�	�\�`v#_�\:@���Ih��R2A��g�	I���OS>��Gn$����a�7��}�������c����|2�"�v�[�\?������t��&���0�7��j18/BMEsM��q������_'U5gQC���$d�{_F�l����w6���BI��d��7n��~�|����4��n��b���l��@>�!�k�GN�����������2��e'y�*m
����D6^�������u��0Yf�
��;h�aG������R*�&�Q��������%���PZ���+�bpe���te�9)P`�}|�]2�	2w�dY���0K���C���nK��S�\��I_&h��]ph��yB\_A�9��OR����I����S�];�z��5\
��� �n�7tF'�Ed��u��j\�;v ����C����5�a���a��%������xik�����T�r#�X[^�C`����u��\���M�
�{o��4O����F�l���b��:�����9�	��;f�]�.�K�����!�qRb�%6IJz�ZTp���Y��e�zM�_u���1a#+���y5��M��"�&�_�if��'g��d�,�<�����l�!V����W8�:h	^6n�8�<F(i=�O�F���fRR+�zo��I�
G���+�j"���������Tf����4ud���M�{Gv#INlbQY|�m������!��+_,P��]y��mW����.�����I�;T��ybd�]����<���3�����kO����'
��
�d)����T��0�����Z��7����'����K���P	0>��"
��ab� +���x������x��,�RGu��0��������X�"�h	kj���%P��T���mG��:#�	.&�6�	mYM0�BT����gF��h�����J�����5�������fg$���)���������vDM��u��Y�q�t������uD����U��q��3��l�y�i	Q���z�yL��M
O�����1;�A���Uc���a����1z�I��&5(FN�����G�/^�����kE[(�qF4���T#��T7��cz�V��\�?^'�v�L4c��U���YW��O�1��T���{��%�!����d�.�D[�r��d����3�k�����9>�9�����I��:T�����X�q7�R+�����Sy]�H9��1�!�%i�R��:���g�o���B`n�������k�Y��K�C�2��l7D~�*�X�A$���A�HG�C��-���$���5�	k Oh9�-�g��n���oQoNI�UR�f�����kT���4��4V��E##k`>�F_���
�@!�03�������u���V6�N��������?&�u�u|Q����vh=��Q��u������E�\�S������7������-�q�������UpT�
���5�������A ��L��K���x#��B��b�E��s4���#C������K=���M��eg��2ED��(i�g�''!���g���Y]o&-NF����O�T;���|<b:�I���7n	P��_�~x�������w/�;�~���7?;�p}�����������~���������~x0��O�K|>�}���������ys�/_�wz��?>���?��Lr`�������w�d���a	V�n���?�i��X��L|��N>����������g������� ��w'�����>z�N-���N�������{8���G��q�P���|T��������'_�����������Kv���=^|�m),��}NT��1#x�������$�wo?�rz����y{M����v�����iA�L�d��p������^?����H��l?�_�?�,���G����7�w�] ;��`~zw{�~O�?�_���_�oX������������C��x6R�m_zx����/o(}�U���^��J�;��[�_�8�c���KZ#P���M�e��q�����M��2�3��B�����e�l��C5��+�.
��J��g�@��d��\�&	E"�����*
&��][�j�q���R��t3������3�Y6p{k�����(��������hV�g���g�$_6:���>�h�r��USI�e���7��|� ��:��M��^���z�;�oG�������`Y�s�h�nmm|M�.�MW��\)I�M���Y@�C��"[TqISU>)6Ty��v��S�e�-i��:=[�`����Q<�%H)M�>	���^J������0��PWI�M7�<��i�K��B�RE��$��)�M����:�n��;��N����m�����,�PW������vKO���q�N��c��Zz���#�����F����R����%�u	��j5
�"7om�\ku�����W����t	�Y�:4��,y
��@3�E`o�����$��H"�Dc�,|!������#v�Y+�������i��bRF]��F���@g.��Z�`�������{�(x���k)=Xn�������Z
X��8�0�H���M�)���D�����?S1�TCL7���Zgi���.c���r7�2��R}������A]�z2\�����=��
�U�%�E�~Vd�A��8h��^��^,������^|R��0}��^�B��x ����
���I2;��T�nCJ��"���b��8}m�`hG���s���t���
#�g !��k��Pf�p!��;�]��T
�f�p���
�=�j����i��G�����I�t]�
S�=\6]�c5����MS}�{���=�#����C8�C*�>~�-3qP�o��)���T��{ao#~���h�+l�B��{qq�����w�$Wx>��b����5C�t;�����Gb�����\dB������x���[���f��?��y	!���H,S�z��x4.;�-)Y/�h�=fF�_6J�������e��.�&�2���Ic���\�\6]�=g�V&|�t]��LL�*�����f4�T�����'���-�!�pL����~�����'���%(�z��+�������W3�O����������\�6����-�T��9�-��Xtl�#R�AS�Fh�S���vq����[F�����A\S�d9����G��f���_�];ZE�t����S\MN����R\�B��/Q���=����s�+����\�7T�e���pW9��`�6�����w�tL����C������y��-��v1��\����K����K���M���H	����D>�i:r�$�x>P�B�p2Vk'�L�CE��>XaFk��
����e����q��%%���h�]H��MY  �2��l�jt�V��
oc��W��J��l:p�����4��5p@��}�W���y�����v]�������l�6�-1#��=����E��<�5�=������C�x�&�m�����1{��=1G��,������K������1$R���S�'����K#�*qD6�G�/;/��V)i��\v��������+G�3�N6%n����a� ^��Zj��3�G�7���b�S���{��t{pL��Z�=�2�|��H��t_v�e/	f����Q���qMx�]�>F�	L��w�ve���k%�=���(�l������)F��Hl���DPZb�������{J��V�s���B��{`���y	
|�� w�5�5T�	�����0��_LP�CZ��=�=|hh��eH�J����v!�3BQ1�>D ��rC/�7�`��-��Ke=�d;������Y&Hy��N�#:�.���R�����;kW��~�^�I�L���%���43��:��v]!����d����d�!*a-'�|��)(7��4Cw(���z�P��S��$P�~�M���W3�'����
8j�oOL���|P��q�`s��m�:c�Y'^�%lJ�$�98mB������`�Y ;�����ReK�K�q������zJ:e�^+;����������H)~������]��j��
�zo��/1�U�����|�#�������|���q3�����	�U�
�v	��������bI���b��<YF�<;��|���Z�1R���9�{�>��^Zj��"��FJ�c�rP9fb��%Qw;/1�#gE���+�22�84���2��9�l�]P���8�	u�����t�H��2*����-�"����l;����>x�!�^��������/y���axqZ�:��x]�5�UvN����1�D�J	�l�2Z�Io�r������,R�:D'�~�������rN�8cs9]i����=���Lb��
�
`��o�
*��~�!�`p�F�Gnx��������E���J��v;@!�/�r��|�c[�W�v����C���c��P{��m�����dc,{fA���c;v,c�x+���-����$����� !��4:��z��.�[���Zf�b�U�g|�^�^����d�.�� X���l�2`c���f�%���e�C�"f	q�Sv���7�2�	�������A"����}KK�,�_���E�A]��:�=	��b\*��L��k�v��=t�/Z�����!4dNh���v�<@@[����v����MY����W�-����s������]:RW��m������LLE��Sj��K��.c��
�}���!;��![��8���d�r%��q�rf�'�u�g�+�����X�������&�.������7�1�0���XX7�;�k�e�t���q}M�sb��������W3�`V����{`O\d���,���$Qwg:�n9|��S�
!(�Wj��B582c74�^9�
JI��q��m��e��e����P�����=��|��}f1\;
q�5K�z!%OA������[upF]/-���5,/57�� 6h��[)��|]:�'�E��������j'ze�uqs�����"�9��.!3Hu7V9
��a�D/��s�]��$����7F��2v���E0����:�,4p(�u��p{�v�v�c�q����R���d�����,	�m�aK��3v�jh��Y�s�y�R�o��,��v����JGK�8-xrX�����eM�{P�9

��b$U.�l�=�f�?Bp�-*�ed���X:���������~���>�����}������qV|-kC��0V*d���������r�I
���1�����kr���}�c,�Y���u'�X	�Z,'�MZi�zKG.����<{�����i���e�{u{oMD�L�&�8��p����V�����4��:T�M���A�_vV	�Uh���h`�����N��u���t�Hv����.-:����q�]�����vI��i�$o_���M@���8���$8����^7�h#�2���������i��f����������`����P5rO��#������"�=��q�FB�*�#Z_�VB��������zA
"���	���p{���;%UA�R��.&�;n��L[O%Y�M��=2c�9��-a����e��

�7iW���#��v���z��T��)��]B�����4���2���?c���j���b��0�&��wy�QXm0Nk��L�v�B%��M�e�SJVwD,�F�q���h���X�B�t�ZDt&��[�RN��6��_�x�J!X����U
-2%���C��S�Hp���L��l?c�=�	�������}�)� ��e���-0���b����C��"9�.T��d��";#9e/1� ����X����wN{�����W������g���A��XR\�<W##��z������dws�q�|��r]�P��X6]��V�l���k��@�bw�tP����CvC"�To�'�EQ�S��D`���u�=���II��N�b0~�����{���F!#{�y�>�"7��q���o�
��_{���"���e�v���d����J�K5Zn���(�����h{B�Nv?���*��b�Tt.^������0���a�E\n�������11����M�u�kV��f�u�n4�����;�������J���"�8��I�z����6n���FQ�1{����wR:���^��E�_���Qg10����z��|��e��i����t0�C��e�"4�H�2r8���Z6�����ri<��@��9�9����
�_:>v��a��f�������`�=0��T�a�!8)�8�H�}l�@D���1'U�J��v]�����l�.tO�T�2�t]l���R�2�.��C�7[�I�Eh�"Dn�_������U��"�gP"o#�o���b^��5�{�l���	h���w������4[�|B�6�M��4c���)G$>p�{���t����Q��!�uh�`�]@���FkC�j��{
�6(���~�����S���]N�th����!��4H�aa���K2��H|�Q�{�x;^�q:������yS['~�\�W-ui!�d�6�W9<-�.��.Z�P�H4,�F_�H9����)K�H�����G�����N|K����t��x���o�[AOvJ�6�42<�.T��d�,���%��.B}E,���\20�<��8�H�UA�MU�B���L��ch�|�p�����$�W&�� ����y2�k����IB�
�7��#�:������&M����j�����;X���]j\wU�M�����l2\�j�>��
pN��Jj��&b9������W=�����H�vo��Z���}��\\�,�����}�W�.�97�b�p�Ga_��"�A���>�@���e���#��C���B:h��x6�.��?����X������nW�5�?KH<��#�l����cf��e�b*�]��s
j;��������Cs�J�����."EHUH�������qDm��>�l�.r&�R'2�.�����v(G��-�d�A��Ed��;dJ{v.����!��I��m)u�'����5�{`?���N��=��Opp�4.�<����r��d�����Zpi�����j��-�<4l^;��!W+7�m��e�q��
�(�O��eC�<|cxy�~j�F�r��d;P������Y#�+��\/��������-��`FVc�+)�O������{��t],O���r�d�.\�H&���j�v�����CQ�Z��)����iL}K�w���q_x� F���1��2���{v�Y�QK2�'/+�������	�r�d����Hhm�@��Fiy����p�q*�R�x��,�c�9#����=`)�����\F�wY��J�<�nO���m�0#e��G�<
k���""��������j=8g.�P�0��v�b�������l���R{WeKc�]��B�(�l��5�,���l�<1��� �e��O���L�_u����P9�,�Q&X�	��?	ZTBJ���'yH�j�,��Q�����vC�D�����Zk��f�{6������d��0���h&����RV������F��3�k+�'����mG��~F�%P@H=��x��$Kb�{+co�nv��'�1�P??���'m�J�<��\�.��J������8�&W�JN���c��q�����"3������h�2�&����'���X���R	�IpI��5�����x�x�"����]��k�Y�TD6�V^�N��
��
1Xg=��Zf�����F��q�I����������u��"����W
p�$&G�������p0�J����mG��:c��i�$o�J
�����M����1��V�����NBL[+�����s�p�*�!LWF���T?�lA�������������y���6j�T+�����.�1u�#�
"�;JraQ�s�O�Q�bL�Z���7~}���y?I����m�C3��4����m���%�C������I*����v�h����6�����_��S�a�����[Yh�M�@���B���zvTz������#���MM���D�j��'�u	i�_���qT���W�������1f�u4y#�6����h7��3Kx��YD�=6T
'6j!=U����y��8�3���,��=�	$lEmz
Zb�zhO�p-/=�n>��U�6��(;�2S�x��*CC����+%A��a�m7k����8�hZxi�gi
��7��%����;6���Zf,��4H6�H��t|{�4�Zx}��lx�B�2��
9*er��_��:x�C���O�������P9������kL+9��v�&�����|p�i$PK��m���;�T���LBpq?��C�@.X����?ZN��1u�s6��{��3��3��L��[�d�!���%M�R�:�t��;K}.,u<T��N��E3��	����W�
��K�A6��E������z3iqdS�
z�P�i�+��2'U'
bg.
 8�s:���{����ow����������/:{�������Y_�d�����r]�<W�tf�u��k�J�9����kR%v���@w���Y�"M�����p�V�4����&�����}L�lCI XX����k4b��"���N����4��������W�D�
�X�i�}�4����K�
�\�;4|�2���������<f�i�dKL�'�Nl������y����z�MG@�~�
J��#�4��m	
-��l�c�����V������'��t]�>
��!��kC|���l���t��Y?#n���1���m�v��F�Fb�q*Q��?DO<�n�&���ED�_���El'$"���w1
�!��df�R�i�7�h�!���M�^%�������qd������\Y��M7��0'�I�yAiRQ���#�����L���=�d����]��	�jp6�o!O ����5q	}`�;��y:��MwW��}d���c�Ty�����8X�v�����`�t��C#�@<��K���5���j��
��O(������erWB�O�����j��1�4�{z
L�{r����e��m��s6�C�Y��W ��Y�5���sc���Z[�{0�0�qNP�h*ej��v#w��T�h=�1�j��l:�&���|�����&
|h|�|S�U.�I�v�
X�>��tut-�O�[Q4]�O�s��!����������GB�9���I��$����B%�4�"���S	�y+m����"�����5!�������"�'o^��s��"]��Z��L7�F~�����(&X�RZy224�����C9�n�<#����P$��%%����3Ei����vu�y{4���ul=Yq��T	`x�=~��G�����N��`�.�1��<���H
9��;���SN��w��t8<��Q����R-\`*�^P;s���T���jYGq��R?U�c���������s<:0"����V�s<�v8<4g��R>#�n&t���c+�	�����'MC�����R|6�n��������y!�V8r���#��h��l%��M������\�x�i@r�t4��|2�(�~��d���� T�kg�uegb�V�����0:B��J����L��nRNs�]!�wL.]���������n��k�?��J�.�#%�J��R�r��G������ 	�I�O����,Z���Q��&�
��Xz�Jn��'n%���.�>��\��~��r�Hv3"Y�"+�� M:i�dm�����"��Q��MG�~��
�4
�q�k��
c����a�v�U��T��J��M����S��:��h��-W������u>�K�W��o�� �I8�5�cX�4FH�;J�"a�Z�B*�k�F��K����������4�-���[�Ka�O����.�)�F|���h�J�$�����Z����� �ph��]P3Im�_2�0����vB<S�
MI�����A�3�mLy���� ��#�x��@������TZ[Z nd����|>\L�������%����7�t]6\��Vmg����P��{0]+�I^�w�{!:E���E�
X�Y���c�����0i
��Y��D��&���B"�����
��m��I&<Q�{>����p5�����t;4�a��#����+M�:�������������J��l�a,��X�����Sb�1p(c�x+�[��J��l:N�4g�����zi�x��\��^9���2y0bK�c85��+c����k=���15v��l;�m�E��_9�4�.��'U2�!�e�ZpLB����N�~l�"������������lK�]I��U�����O7<�����mx�1��b��c\(�7���r���?c�wW���������gPC��
����kt��v���a
HcT�7������K��K���b��P(Q8^0�tD-3��IZ��:�J�Fade��C������>c�X��SO����R�^��������z^� �m�E�?3W���m�������������
y��i���E0����W���l��sq/=�ROvk�S����'i9�?*2�e��t�y\	�V[���m7�:c���z��MH:�-;4�^?�E*���v�hf;'�E5��
����Ff����A���<�V
�N�`k�9k��X�X�e)�)[?ckv����'�]0b� 6]�D�B��zf�]�F�[\����v]��I��T����4�.�y�a�0I�K�G�I�kS�����Ce�l��%��["�����?��L����	�|0����.M`�����%�_�(����K��ji��{
����1Tm�PJ6�0������\�_D������kCv�y��]����v,�����%B,����B�<��:��Q�}I�P�����f�O����j����mI������,��R���J�'Kq\�H)�R��}�n��N{��V7g4z�Z+�,z yx�����j{,Yne��A�j{8r�V��>������Z�]s��������wS7K�D����!+��T�zk��v��.���C gA@![��������������W�����������B7$��;;���^N>~���A������5����!��+3�S�R��b�����w�����s���I�4���1��X�<{dqK�������q�f"�����Oq������4���?�xg�c�}��|Zkl�~4><{��������v��U�LW�m�������P�t����w�Kk����~���R��Q��y��"�w�����t�=��HO�|����������^�n[���y�C��<�[��qN�r��WS8t���@�����4/�YX��7u"&����u�0J�n ��>�{�$��?����$���fKO��<p#�Q��u	�CK�?46��}5?�Z�������t��@�6����10�6q��� ����k��@X�U�2�^�]~i)�ni�������?@9�GA/=����.�Ret��o6��OD��@�����Gz�w=8�������O!�!�T��� �zX
��#�������l�h6=1���Xv���4Y��up#��nP��Q�
<�P��O5G���(��SOyc>~��
�G�������qtOm����(����X���{�L�G�c�>&����s��������7VwV�}PtUD�5�h@`E�"ZD�����Q�^�hZ������[H(�����N2���/D�%T2��pG��`/T�^��Q�:c��x��}z�:��@|}FC'��P{S1ov#O�L�&3^����f�����6����O�J�K�6K(���];��u��A���T5�|fnM��v�mqC���5�|8s���%
v���>?|�����/?o��/�������+Y�cq��5�����SIi���x����dy,C��6��������;�nV�tToU��V�V���,�!v-�9qv��m�@p���l���Oj��'���7�DL�X��.A�4����3�3���f��m]J��D�*J\�fh B,s�MO�e�����E�l]�0��8�����%�nq'���l�4v�g�N�>23��t��=���^��Muk��1�����qI��w���������p��l��> ����
��B�'e^�}��a�<�,n#�(q��,�JUb��G�p������Cp�������=��F��"L����[Jw@�!�L�`�b�:����U��Cz�����@����
A
(H-�b����j����u��P�~�jy9mz�Z�MG���!A8�(��j]����6����dw�J'��G���&�c���;[�<8��vlS��,wA�^��|���2F��W)yO��r�������_�3L��3^�;��N�����LM��c�����=�tW)����-���R��5 ��$<n]#DWK�=�Z���4;4:>�<�������z��[v��r{�;��^��Q)���Gi//�Bk��(�i��`Z����2u�T�l�B�o7�{P�;
�K����$��,����Dx�;i��M�}X{����=������������3v$c��\���E�������O_�8�2X/��e���
���9o��XR�Z������`���G�|Q�n4�N�H�G���"�ZG!����x�8�����f�����Nb:E�*��S�
����Q9�;	���u��l:|�
����.1u����Q�N��'d�T�����f�c����q#�M�%�1Dm��g�cq�4?�y������*��
!�I�&&"ZT�8p������k]����������i�'��p���$�$Jo=z'lA�Bn�����=�+7�9��'�	_��b��y�v��y��"�;B>/��=���XF�-����b/�A��P�9��0>����O�����8!nH+#X�;�H���bc���O��M�%X���l2g���&����x��C�g�cq2^�1y~�����OQr>�N(�|�^��"�K����=u���c���*�9�D��5���Y���%o�_��O�`l�?�v����{�����0��K3M����D�Ar0/�hj�sjrx1�O=5

�O���Zm6����%��J�
���x������G��R�\�s�.�-G@��!��qS���t�S���u�B�a�u�K�{���n[�f�c���8����D�<�"���X�?�
���!h����
�W�y)�h�d��=�Y1�m�F��F����b�PZ�:n.�{�"����$Q��	::Q5#)���`��"�9`�����Dh�/{�Xx�2�������u��j4���,O�d��eT�j"u�c�KE���|����,x��K�q�����!/�W���p3��j�n��Xp	�T�jN������m��M��FY������P�J�y#E�-w��qM+_���XFMSR��"=�1�>��z���I�x^�5Z���/�����f���	���0�R��R}��c����%�S0n�7f���D�R����^2v��Z�y>2

�O��J��Am�TMv
j��I�D��� ��N�7��������1�)86v����H[��-�*���#?���G������$xm��}4>�E�E-���L���3x�F�?��9��f2�V�I�a4W�B��4�Q��:z~����c���*�
PM�$a���u����o6�����x���x��9��8zD��0y�$�dz"������a0`N=Y�3.�,���G����������f��9m�H"�VOh���@+�����������h$P���Y�'�G���c�=E�1��b�JQ���3;w�~|�>�zf�'GW�ca=YH�@2>�w'�cqs>94��L���f��y���yLX�(e�u�������w��[���0������/��6��l���4${"�������a��Ly������D���������n=Ef�.��#������@W���a}X{,7�Q����t��xddp�����y�U[h�vc��\1DE)"]�o$�zaw��wc��'	�(�3��S0j����2�$�[o&�co0���=���.�X�p�<FJ�B�@��CL)�������^� \n2L���. �F�e�Kw[�gg�R�eL��	�/��^d�-
���F�pD�=+���c��<������0phe�+���YBjTd�MO�e���'q�&�S�58��u�`��R���G:���eC^`�B�������5�7��m����l�a��~������Dm������1�5�u���l�X^^m���U�W�R q�d��X�"w�.���,p��������t��$���f��h%�s���H�v//_��G(z������DX�R0eXA�bRR��)�
+���g�@��������
A�@b����'�5���-�J�<.��$���j;�����8�Q���}�y$5u�������h�RQ�h|��1C$�����n5>�l����T^����Cj�KBc9A��RSU>�J��s�'��S5��:'�.-�T����0	��<EL��	w>��?�N���o��Dxq���q[ts>S������[��I���b�����|>2�K��!�64����>�'5{0vM��G���������g�l�iT8�M��D����'d#\�
�:�����
�k�I<$m%&���dt,"uM����>��|�l����X�\�ic{�M��=W�3Z#xT.�3��g]�~�s�v���Zd�����y����s��|Nh�j�-���$��,j�-�g���Z��F�E�A�U.���>dCZ��Q(�O�,yI�����H����|H��G��'�u�����0��]�z3����O�������U�G@��eO(�A��|��_����5yz�7iE���a����#5C����bl�2�j	a=��g�c��X:,����������}��v�]��h�8�`>D���#�]��N����yG1M���qQ
�wa��l���P�L�k6r�;C}��R�5;�����'b���BK���n����@#c��
zo�B�q�����y��!�I�4�HF��I���@��?����_d�����jC��=Oqy��l��}R�mD�x�gO1;�-������pOET�-�c�������w�|������xU��������/~~~�����_}z�������{�����(^�?��������b�����C�����|����F����[3�)sB~���
��wo��pn~������t�6��������;����7������cX������U>�l�P~q�����������������w����w��?�������O�PK��Et�rO�PK��P�l9�..mimetypePK��P���[ppTThumbnails/thumbnail.pngPK��P���{�+�settings.xmlPK��P��h��
manifest.rdfPK��PBConfigurations2/images/Bitmaps/PK��PConfigurations2/popupmenu/PK��P�Configurations2/statusbar/PK��P�Configurations2/accelerator/PK��P)Configurations2/progressbar/PK��PcConfigurations2/toolbar/PK��P�Configurations2/toolpanel/PK��P�Configurations2/floater/PK��PConfigurations2/menubar/PK��P��l���=meta.xmlPK��P` ��'
)styles.xmlPK��P�u��1,GMETA-INF/manifest.xmlPK��P��Et�rO��content.xmlPKe��
#16James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#15)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

James

#17Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#16)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#18James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#17)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

James

#19James Coleman
jtc331@gmail.com
In reply to: James Coleman (#18)

On Sun, Apr 26, 2020 at 7:41 PM James Coleman <jtc331@gmail.com> wrote:

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com>

wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <

tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash

joins, which I

think ran into mostly the same challenge of deciding when the

bloom

filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where

you

write the query as IN(VALUES(...)) instead of IN() compares. It

would

be interesting to know if the planner is able to make a more

suitable

choice and also to see how all the work over the years to improve

Hash

Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison

is

very cheap and there's almost no difference. For "text" the

comparison

is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and

10%

match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest

we

should not be doing binary search and instead just build a simple

hash

table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

While working on this I noticed that dynahash.c line 499 has this assertion:

Assert(info->entrysize >= info->keysize);

Do you by any chance know why the entry would need to be larger than the
key? In this case I'm really treating the hash like a set (if there's a
hash set implementation that doesn't store a value, then I'd be happy to
use that instead) so I've configured the entry as sizeof(bool) which is
obviously smaller than the key.

If it helps, that line was added by Tom in fba2a104c6d.

Thanks,
James

#20David Rowley
dgrowleyml@gmail.com
In reply to: James Coleman (#19)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Mon, 27 Apr 2020 at 15:12, James Coleman <jtc331@gmail.com> wrote:

While working on this I noticed that dynahash.c line 499 has this assertion:

Assert(info->entrysize >= info->keysize);

Do you by any chance know why the entry would need to be larger than the key?

Larger or equal. They'd be equal if you the key was the data, since
you do need to store at least the key. Looking at the code for
examples where dynahash is used in that situation, I see
_hash_finish_split().

David

#21James Coleman
jtc331@gmail.com
In reply to: David Rowley (#20)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sun, Apr 26, 2020 at 11:44 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, 27 Apr 2020 at 15:12, James Coleman <jtc331@gmail.com> wrote:

While working on this I noticed that dynahash.c line 499 has this assertion:

Assert(info->entrysize >= info->keysize);

Do you by any chance know why the entry would need to be larger than the key?

Larger or equal. They'd be equal if you the key was the data, since
you do need to store at least the key. Looking at the code for
examples where dynahash is used in that situation, I see
_hash_finish_split().

Ah, I was thinking of it as key and value being separate sizes added
together rather than one including the other.

Thanks,
James

#22James Coleman
jtc331@gmail.com
In reply to: James Coleman (#18)
2 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sun, Apr 26, 2020 at 7:41 PM James Coleman <jtc331@gmail.com> wrote:

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

I've attached a patch series as before, but with an additional patch
that switches to using dynahash instead of binary search.

Whereas before the benchmarking ended up with a trimodal distribution
(i.e., master with IN <list>, patch with IN <list>, and either with IN
VALUES), the hash implementation brings us back to an effectively
bimodal distribution -- though the hash scalar array op expression
implementation for text is about 5% faster than the hash join.

Current outstanding thoughts (besides comment/naming cleanup):

- The saop costing needs to be updated to match, as Tomas pointed out.

- Should we be concerned about single execution cases? For example, is
the regression of speed on a simple SELECT x IN something we should
try to defeat by only kicking in the optimization if we execute in a
loop at least twice? That might be of particular interest to pl/pgsql.

- Should we have a test for an operator with a non-strict function?
I'm not aware of any built-in ops that have that characteristic; would
you suggest just creating a fake one for the test?

James

Attachments:

v2-0002-Try-using-dynahash.patchtext/x-patch; charset=US-ASCII; name=v2-0002-Try-using-dynahash.patchDownload
From cd760f5d333a2f1c7b871f05d4a231c0381c660d Mon Sep 17 00:00:00 2001
From: James Coleman <jtc331@gmail.com>
Date: Mon, 27 Apr 2020 08:51:28 -0400
Subject: [PATCH v2 2/2] Try using dynahash

---
 src/backend/executor/execExpr.c           |  29 ++--
 src/backend/executor/execExprInterp.c     | 197 ++++++++++++----------
 src/include/executor/execExpr.h           |   7 +-
 src/test/regress/expected/expressions.out |   7 +
 src/test/regress/sql/expressions.sql      |   2 +
 5 files changed, 138 insertions(+), 104 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c202cc7e89..6249db5426 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -31,6 +31,7 @@
 #include "postgres.h"
 
 #include "access/nbtree.h"
+#include "access/hash.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
 #include "executor/execExpr.h"
@@ -949,10 +950,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
 				Oid			func;
+				Oid			hash_func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
+				FmgrInfo   *hash_finfo;
+				FunctionCallInfo hash_fcinfo;
 				AclResult	aclresult;
 				bool		useBinarySearch = false;
 
@@ -983,11 +987,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				{
 					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
 					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-					Oid			orderingOp;
-					Oid			orderingFunc;
-					Oid			opfamily;
-					Oid			opcintype;
-					int16		strategy;
 					int			nitems;
 
 					/*
@@ -998,21 +997,24 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
 					{
 						/*
-						 * Find the ordering op that matches the originally
+						 * Find the hash op that matches the originally
 						 * planned equality op.
 						 */
-						orderingOp = get_ordering_op_for_equality_op(opexpr->opno, NULL);
-						get_ordering_op_properties(orderingOp, &opfamily, &opcintype, &strategy);
-						orderingFunc = get_opfamily_proc(opfamily, opcintype, opcintype, BTORDER_PROC);
+						useBinarySearch = get_op_hash_functions(opexpr->opno, NULL, &hash_func);
 
 						/*
 						 * But we may not have one, so fall back to the
 						 * default implementation if necessary.
 						 */
-						if (OidIsValid(orderingFunc))
+						if (useBinarySearch)
 						{
-							useBinarySearch = true;
-							func = orderingFunc;
+							hash_finfo = palloc0(sizeof(FmgrInfo));
+							hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
+							fmgr_info(hash_func, hash_finfo);
+							fmgr_info_set_expr((Node *) node, hash_finfo);
+							InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
+													 opexpr->inputcollid, NULL, NULL);
+							InvokeFunctionExecuteHook(hash_func);
 						}
 					}
 				}
@@ -1042,6 +1044,9 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					scratch.d.scalararraybinsearchop.finfo = finfo;
 					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
 					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
+					scratch.d.scalararraybinsearchop.hash_finfo = hash_finfo;
+					scratch.d.scalararraybinsearchop.hash_fcinfo_data = hash_fcinfo;
+					scratch.d.scalararraybinsearchop.hash_fn_addr = hash_finfo->fn_addr;
 				}
 				else
 				{
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5bebafbf0c..e54e807c6b 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -178,8 +178,6 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
-static int	compare_array_elements(const void *a, const void *b, void *arg);
-
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -3593,6 +3591,51 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+static ExprEvalStep *current_saop_op;
+
+/*
+ * Hash function for elements.
+ *
+ * We use the element type's default hash opclass, and the column collation
+ * if the type is collation-sensitive.
+ */
+/* XXX: Name function to be specific to saop binsearch? */
+static uint32
+element_hash(const void *key, Size keysize)
+{
+	Datum hash;
+	FunctionCallInfo fcinfo = current_saop_op->d.scalararraybinsearchop.hash_fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) key);
+	fcinfo->args[0].isnull = false;
+
+	/* The keysize parameter is superfluous here */
+	hash = current_saop_op->d.scalararraybinsearchop.hash_fn_addr(fcinfo);
+
+	return DatumGetUInt32(hash);
+}
+
+/*
+ * Matching function for elements, to be used in hashtable lookups.
+ */
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+element_match(const void *key1, const void *key2, Size keysize)
+{
+	Datum result;
+	FunctionCallInfo fcinfo = current_saop_op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) key1);
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = *((const Datum *) key2);
+	fcinfo->args[1].isnull = false;
+
+	/* The keysize parameter is superfluous here */
+	result = current_saop_op->d.scalararraybinsearchop.fn_addr(fcinfo);
+
+	return DatumGetBool(result) ? 0 : 1;
+}
+
 /*
  * Evaluate "scalar op ANY (const array)".
  *
@@ -3613,16 +3656,19 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
 	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
 	ArrayType  *arr;
+	Datum		scalar = fcinfo->args[0].value;
 	Datum		result;
 	bool		resultnull;
-	bool	   *elem_nulls;
-	int			l = 0,
-				r,
-				res;
+	bool		hashfound;
+	int			res;
+
+	/* If we're only executing once, do we need a way to fall back to the regular loop? */
 
 	/* We don't setup a binary search op if the array const is null. */
 	Assert(!*op->resnull);
 
+	current_saop_op = op;
+
 	/*
 	 * If the scalar is NULL, and the function is strict, return NULL; no
 	 * point in executing the search.
@@ -3634,104 +3680,88 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	}
 
 	/* Preprocess the array the first time we execute the op. */
-	if (op->d.scalararraybinsearchop.elem_values == NULL)
+	if (op->d.scalararraybinsearchop.elements_tab == NULL)
 	{
-		/* Cache the original lhs so we can scribble on it. */
-		Datum		scalar = fcinfo->args[0].value;
-		bool		scalar_isnull = fcinfo->args[0].isnull;
-		int			num_nonnulls = 0;
-		MemoryContext old_cxt;
-		MemoryContext array_cxt;
 		int16		typlen;
 		bool		typbyval;
 		char		typalign;
+		HASHCTL		elem_hash_ctl;
+		int			nitems;
+		int			num_nulls = 0;
+		char	   *s;
+		bits8	   *bitmap;
+		int			bitmask;
 
 		arr = DatumGetArrayTypeP(*op->resvalue);
+		nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 
 		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
 							 &typlen,
 							 &typbyval,
 							 &typalign);
 
-		array_cxt = AllocSetContextCreate(
-										  econtext->ecxt_per_query_memory,
-										  "scalararraybinsearchop context",
-										  ALLOCSET_SMALL_SIZES);
-		old_cxt = MemoryContextSwitchTo(array_cxt);
+		MemSet(&elem_hash_ctl, 0, sizeof(elem_hash_ctl));
+		elem_hash_ctl.keysize = sizeof(Datum);
+		elem_hash_ctl.entrysize = sizeof(Datum);
+		elem_hash_ctl.hash = element_hash;
+		elem_hash_ctl.match = element_match;
+		elem_hash_ctl.hcxt = econtext->ecxt_per_query_memory;
+		op->d.scalararraybinsearchop.elements_tab = hash_create("Scalar array op expr elements",
+								   nitems,
+								   &elem_hash_ctl,
+								   HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+
+		s = (char *) ARR_DATA_PTR(arr);
+		bitmap = ARR_NULLBITMAP(arr);
+		bitmask = 1;
+		for (int i = 0; i < nitems; i++)
+		{
+			Datum		element;
+
+			/* Get array element, checking for NULL. */
+			if (bitmap && (*bitmap & bitmask) == 0)
+			{
+				num_nulls++;
+			}
+			else
+			{
+				element = fetch_att(s, typbyval, typlen);
+				s = att_addlength_pointer(s, typlen, s);
+				s = (char *) att_align_nominal(s, typalign);
 
-		deconstruct_array(arr,
-						  ARR_ELEMTYPE(arr),
-						  typlen, typbyval, typalign,
-						  &op->d.scalararraybinsearchop.elem_values, &elem_nulls, &op->d.scalararraybinsearchop.num_elems);
+				hash_search(op->d.scalararraybinsearchop.elements_tab, (const void *) &element, HASH_ENTER, NULL);
+			}
 
-		/* Remove null entries from the array. */
-		for (int j = 0; j < op->d.scalararraybinsearchop.num_elems; j++)
-		{
-			if (!elem_nulls[j])
-				op->d.scalararraybinsearchop.elem_values[num_nonnulls++] = op->d.scalararraybinsearchop.elem_values[j];
+			/* Advance bitmap pointer if any. */
+			if (bitmap)
+			{
+				bitmask <<= 1;
+				if (bitmask == 0x100)
+				{
+					bitmap++;
+					bitmask = 1;
+				}
+			}
 		}
 
 		/*
 		 * Remember if we had any nulls so that we know if we need to execute
 		 * non-strict functions with a null lhs value if no match is found.
 		 */
-		op->d.scalararraybinsearchop.has_nulls = num_nonnulls < op->d.scalararraybinsearchop.num_elems;
-		op->d.scalararraybinsearchop.num_elems = num_nonnulls;
+		op->d.scalararraybinsearchop.has_nulls = num_nulls > 0;
 
 		/*
-		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
-		 * rhs values are guaranteed to be non-null.
+		 * We only setup a binary search op if we have > 8 elements, so we don't
+		 * need to worry about adding an optimization for the empty array case.
 		 */
-		fcinfo->args[0].isnull = false;
-		fcinfo->args[1].isnull = false;
-
-		/* Sort the array and remove duplicate elements. */
-		qsort_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
-				  compare_array_elements, op);
-		op->d.scalararraybinsearchop.num_elems = qunique_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
-															 compare_array_elements, op);
-
-		/* Restore the lhs value after we scribbed on it for sorting. */
-		fcinfo->args[0].value = scalar;
-		fcinfo->args[0].isnull = scalar_isnull;
-
-		MemoryContextSwitchTo(old_cxt);
+		Assert(nitems > 0);
 	}
 
-	/*
-	 * We only setup a binary search op if we have > 8 elements, so we don't
-	 * need to worry about adding an optimization for the empty array case.
-	 */
-	Assert(!(op->d.scalararraybinsearchop.num_elems <= 0 && !op->d.scalararraybinsearchop.has_nulls));
-
-	/* Assume no match will be found until proven otherwise. */
-	result = BoolGetDatum(false);
+	/* Check the hash to see if we have a match. */
+	hash_search(op->d.scalararraybinsearchop.elements_tab, (const void *) &scalar, HASH_FIND, &hashfound);
+	result = BoolGetDatum(hashfound);
 	resultnull = false;
 
-	/* Binary search through the array. */
-	r = op->d.scalararraybinsearchop.num_elems - 1;
-	while (l <= r)
-	{
-		int			i = (l + r) / 2;
-
-		fcinfo->args[1].value = op->d.scalararraybinsearchop.elem_values[i];
-
-		/* Call comparison function */
-		fcinfo->isnull = false;
-		res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
-
-		if (res == 0)
-		{
-			result = BoolGetDatum(true);
-			resultnull = false;
-			break;
-		}
-		else if (res > 0)
-			l = i + 1;
-		else
-			r = i - 1;
-	}
-
 	/*
 	 * If we didn't find a match in the array, we still might need to handle
 	 * the possibility of null values (we've previously removed them from the
@@ -3761,19 +3791,6 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	*op->resnull = resultnull;
 }
 
-/* XXX: Name function to be specific to saop binsearch? */
-static int
-compare_array_elements(const void *a, const void *b, void *arg)
-{
-	ExprEvalStep *op = (ExprEvalStep *) arg;
-	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
-
-	fcinfo->args[0].value = *((const Datum *) a);
-	fcinfo->args[1].value = *((const Datum *) b);
-
-	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
-}
-
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index ac4478d060..2e93b1f990 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -554,13 +554,16 @@ typedef struct ExprEvalStep
 		/* for EEOP_SCALARARRAYOP_BINSEARCH */
 		struct
 		{
-			int			num_elems;
 			bool		has_nulls;
-			Datum	   *elem_values;
+			HTAB	   *elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
 			/* faster to access without additional indirection: */
 			PGFunction	fn_addr;	/* actual call address */
+			FmgrInfo   *hash_finfo;	/* function's lookup data */
+			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	hash_fn_addr;	/* actual call address */
 		}			scalararraybinsearchop;
 
 		/* for EEOP_XMLEXPR */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 55b57b9c59..f37dfe1ce2 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -197,3 +197,10 @@ select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
  
 (1 row)
 
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ t
+(1 row)
+
+-- TODO: test non-strict op?
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 3cb850d838..c30fe66c5e 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -76,3 +76,5 @@ select 1 in (null, null, null, null, null, null, null, null, null, null, null);
 select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- TODO: test non-strict op?
-- 
2.17.1

v2-0001-Binary-search-const-arrays-in-OR-d-ScalarArrayOps.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Binary-search-const-arrays-in-OR-d-ScalarArrayOps.patchDownload
From 08742543d7865d5f25c24c26bf1014924035c9eb Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Fri, 10 Apr 2020 21:40:50 +0000
Subject: [PATCH v2 1/2] Binary search const arrays in OR'd ScalarArrayOps

Currently all scalar array op expressions execute as a linear search
through the array argument. However when OR semantics are desired it's
possible to instead use a binary search. Here we apply that optimization
to constant arrays (so we don't need to worry about teaching expression
execution when params change) of at least length 9 (since very short
arrays average to the same number of comparisons for linear searches and
thus avoid the preprocessing necessary for a binary search).
---
 src/backend/executor/execExpr.c           |  79 +++++++--
 src/backend/executor/execExprInterp.c     | 193 ++++++++++++++++++++++
 src/include/executor/execExpr.h           |  14 ++
 src/test/regress/expected/expressions.out |  39 +++++
 src/test/regress/sql/expressions.sql      |  11 ++
 5 files changed, 326 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c6a77bd66f..c202cc7e89 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -49,6 +49,7 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
+#define MIN_ARRAY_SIZE_FOR_BINARY_SEARCH 9
 
 typedef struct LastAttnumInfo
 {
@@ -947,11 +948,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				bool		useBinarySearch = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -964,12 +967,58 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				func = opexpr->opfuncid;
+
+				/*
+				 * If we have a constant array and want OR semantics, then we
+				 * can use a binary search to implement the op instead of
+				 * looping through the entire array for each execution.
+				 */
+				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
+					!((Const *) arrayarg)->constisnull)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					Oid			orderingOp;
+					Oid			orderingFunc;
+					Oid			opfamily;
+					Oid			opcintype;
+					int16		strategy;
+					int			nitems;
+
+					/*
+					 * Only do the optimization if we have a large enough
+					 * array to make it worth it.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
+					{
+						/*
+						 * Find the ordering op that matches the originally
+						 * planned equality op.
+						 */
+						orderingOp = get_ordering_op_for_equality_op(opexpr->opno, NULL);
+						get_ordering_op_properties(orderingOp, &opfamily, &opcintype, &strategy);
+						orderingFunc = get_opfamily_proc(opfamily, opcintype, opcintype, BTORDER_PROC);
+
+						/*
+						 * But we may not have one, so fall back to the
+						 * default implementation if necessary.
+						 */
+						if (OidIsValid(orderingFunc))
+						{
+							useBinarySearch = true;
+							func = orderingFunc;
+						}
+					}
+				}
+
+				InvokeFunctionExecuteHook(func);
+				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -981,18 +1030,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * be overwritten by EEOP_SCALARARRAYOP[_BINSEARCH], and will
+				 * not be passed to any other expression.
 				 */
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				if (useBinarySearch)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_BINSEARCH;
+					scratch.d.scalararraybinsearchop.finfo = finfo;
+					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
+					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
+				}
+				else
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				}
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 113ed1547c..5bebafbf0c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,6 +76,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -177,6 +178,8 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
+static int	compare_array_elements(const void *a, const void *b, void *arg);
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -425,6 +428,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_SCALARARRAYOP_BINSEARCH,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1464,6 +1468,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_BINSEARCH)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpBinSearch(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3581,6 +3593,187 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it binary searches through the array for a
+ * match after sorting the array and removing null and duplicate entries.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR.  Of course we short-circuit as soon as the result
+ * is known.
+ */
+void
+ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		result;
+	bool		resultnull;
+	bool	   *elem_nulls;
+	int			l = 0,
+				r,
+				res;
+
+	/* We don't setup a binary search op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (op->d.scalararraybinsearchop.elem_values == NULL)
+	{
+		/* Cache the original lhs so we can scribble on it. */
+		Datum		scalar = fcinfo->args[0].value;
+		bool		scalar_isnull = fcinfo->args[0].isnull;
+		int			num_nonnulls = 0;
+		MemoryContext old_cxt;
+		MemoryContext array_cxt;
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		array_cxt = AllocSetContextCreate(
+										  econtext->ecxt_per_query_memory,
+										  "scalararraybinsearchop context",
+										  ALLOCSET_SMALL_SIZES);
+		old_cxt = MemoryContextSwitchTo(array_cxt);
+
+		deconstruct_array(arr,
+						  ARR_ELEMTYPE(arr),
+						  typlen, typbyval, typalign,
+						  &op->d.scalararraybinsearchop.elem_values, &elem_nulls, &op->d.scalararraybinsearchop.num_elems);
+
+		/* Remove null entries from the array. */
+		for (int j = 0; j < op->d.scalararraybinsearchop.num_elems; j++)
+		{
+			if (!elem_nulls[j])
+				op->d.scalararraybinsearchop.elem_values[num_nonnulls++] = op->d.scalararraybinsearchop.elem_values[j];
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararraybinsearchop.has_nulls = num_nonnulls < op->d.scalararraybinsearchop.num_elems;
+		op->d.scalararraybinsearchop.num_elems = num_nonnulls;
+
+		/*
+		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
+		 * rhs values are guaranteed to be non-null.
+		 */
+		fcinfo->args[0].isnull = false;
+		fcinfo->args[1].isnull = false;
+
+		/* Sort the array and remove duplicate elements. */
+		qsort_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+				  compare_array_elements, op);
+		op->d.scalararraybinsearchop.num_elems = qunique_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+															 compare_array_elements, op);
+
+		/* Restore the lhs value after we scribbed on it for sorting. */
+		fcinfo->args[0].value = scalar;
+		fcinfo->args[0].isnull = scalar_isnull;
+
+		MemoryContextSwitchTo(old_cxt);
+	}
+
+	/*
+	 * We only setup a binary search op if we have > 8 elements, so we don't
+	 * need to worry about adding an optimization for the empty array case.
+	 */
+	Assert(!(op->d.scalararraybinsearchop.num_elems <= 0 && !op->d.scalararraybinsearchop.has_nulls));
+
+	/* Assume no match will be found until proven otherwise. */
+	result = BoolGetDatum(false);
+	resultnull = false;
+
+	/* Binary search through the array. */
+	r = op->d.scalararraybinsearchop.num_elems - 1;
+	while (l <= r)
+	{
+		int			i = (l + r) / 2;
+
+		fcinfo->args[1].value = op->d.scalararraybinsearchop.elem_values[i];
+
+		/* Call comparison function */
+		fcinfo->isnull = false;
+		res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+
+		if (res == 0)
+		{
+			result = BoolGetDatum(true);
+			resultnull = false;
+			break;
+		}
+		else if (res > 0)
+			l = i + 1;
+		else
+			r = i - 1;
+	}
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararraybinsearchop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/* Had nulls, so strict function implies null. */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* Execute function will null rhs just once. */
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+			result = BoolGetDatum(res == 0);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+compare_array_elements(const void *a, const void *b, void *arg)
+{
+	ExprEvalStep *op = (ExprEvalStep *) arg;
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) a);
+	fcinfo->args[1].value = *((const Datum *) b);
+
+	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index dbe8649a57..ac4478d060 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -213,6 +213,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_SCALARARRAYOP_BINSEARCH,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -550,6 +551,18 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_SCALARARRAYOP_BINSEARCH */
+		struct
+		{
+			int			num_elems;
+			bool		has_nulls;
+			Datum	   *elem_values;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+		}			scalararraybinsearchop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -728,6 +741,7 @@ extern void ExecEvalSubscriptingRefAssign(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 4f4deaec22..55b57b9c59 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,42 @@ select count(*) from date_tbl
     12
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..3cb850d838 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,14 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
-- 
2.17.1

#23Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#22)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Mon, Apr 27, 2020 at 09:04:09PM -0400, James Coleman wrote:

On Sun, Apr 26, 2020 at 7:41 PM James Coleman <jtc331@gmail.com> wrote:

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

I've attached a patch series as before, but with an additional patch
that switches to using dynahash instead of binary search.

OK. I can't take closer look at the moment, I'll check later.

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

Whereas before the benchmarking ended up with a trimodal distribution
(i.e., master with IN <list>, patch with IN <list>, and either with IN
VALUES), the hash implementation brings us back to an effectively
bimodal distribution -- though the hash scalar array op expression
implementation for text is about 5% faster than the hash join.

Nice. I'm not surprised this is a bit faster than hash join, which has
to worry about additional stuff.

Current outstanding thoughts (besides comment/naming cleanup):

- The saop costing needs to be updated to match, as Tomas pointed out.

- Should we be concerned about single execution cases? For example, is
the regression of speed on a simple SELECT x IN something we should
try to defeat by only kicking in the optimization if we execute in a
loop at least twice? That might be of particular interest to pl/pgsql.

I don't follow. How is this related to the number of executions and/or
plpgsql? I suspect you might be talking about prepared statements, but
surely the hash table is built for each execution anyway, even if the
plan is reused, right?

I think the issue we've been talking about is considering the number of
lookups we expect to do in the array/hash table. But that has nothing to
do with plpgsql and/or multiple executions ...

- Should we have a test for an operator with a non-strict function?
I'm not aware of any built-in ops that have that characteristic; would
you suggest just creating a fake one for the test?

Dunno, I haven't thought about this very much. In general I think the
new code should simply behave the same as the current code, i.e. if it
does not check for strictness, we don't need either.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#24James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#23)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 8:25 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 27, 2020 at 09:04:09PM -0400, James Coleman wrote:

On Sun, Apr 26, 2020 at 7:41 PM James Coleman <jtc331@gmail.com> wrote:

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

I've attached a patch series as before, but with an additional patch
that switches to using dynahash instead of binary search.

OK. I can't take closer look at the moment, I'll check later.

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

No particular reason; it wasn't clear to me that there was a reason to
prefer one or the other (and I'm not acquainted with the codebase
enough to know the differences), so I chose dynahash because it was
easier to find examples to follow for implementation.

Whereas before the benchmarking ended up with a trimodal distribution
(i.e., master with IN <list>, patch with IN <list>, and either with IN
VALUES), the hash implementation brings us back to an effectively
bimodal distribution -- though the hash scalar array op expression
implementation for text is about 5% faster than the hash join.

Nice. I'm not surprised this is a bit faster than hash join, which has
to worry about additional stuff.

Current outstanding thoughts (besides comment/naming cleanup):

- The saop costing needs to be updated to match, as Tomas pointed out.

- Should we be concerned about single execution cases? For example, is
the regression of speed on a simple SELECT x IN something we should
try to defeat by only kicking in the optimization if we execute in a
loop at least twice? That might be of particular interest to pl/pgsql.

I don't follow. How is this related to the number of executions and/or
plpgsql? I suspect you might be talking about prepared statements, but
surely the hash table is built for each execution anyway, even if the
plan is reused, right?

I think the issue we've been talking about is considering the number of
lookups we expect to do in the array/hash table. But that has nothing to
do with plpgsql and/or multiple executions ...

Suppose I do "SELECT 1 IN <100 item list>" (whether just as a
standalone query or in pl/pgsql). Then it doesn't make sense to use
the optimization, because it can't possibly win over a naive linear
scan through the array since to build the hash we have to do that
linear scan anyway (I suppose in theory the hashing function could be
dramatically faster than the equality op, so maybe it could win
overall, but it seems unlikely to me). I'm not so concerned about this
in any query where we have a real FROM clause because even if we end
up with only one row, the relative penalty is low, and the potential
gain is very high. But simple expressions in pl/pgsql, for example,
are a case where we can know for certain (correct me if I've wrong on
this) that we'll only execute the expression once, which means there's
probably always a penalty for choosing the implementation with setup
costs over the default linear scan through the array.

- Should we have a test for an operator with a non-strict function?
I'm not aware of any built-in ops that have that characteristic; would
you suggest just creating a fake one for the test?

Dunno, I haven't thought about this very much. In general I think the
new code should simply behave the same as the current code, i.e. if it
does not check for strictness, we don't need either.

Both the original implementation and this optimized version have the
following short circuit condition:

if (fcinfo->args[0].isnull && strictfunc)

But I'm not sure there are any existing tests to show that the "&&
strictfunc" is required.

James

#25Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#24)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 08:39:18AM -0400, James Coleman wrote:

On Tue, Apr 28, 2020 at 8:25 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 27, 2020 at 09:04:09PM -0400, James Coleman wrote:

On Sun, Apr 26, 2020 at 7:41 PM James Coleman <jtc331@gmail.com> wrote:

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash joins, which I
think ran into mostly the same challenge of deciding when the bloom
filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test where you
write the query as IN(VALUES(...)) instead of IN() compares. It would
be interesting to know if the planner is able to make a more suitable
choice and also to see how all the work over the years to improve Hash
Joins compares to the bsearch with and without the bloom filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a very
significant effort though.

I modified the script to also do the join version of the query. I can
only run it on my laptop at the moment, so the results may be a bit
different from those I shared before, but it's interesting I think.

In most cases it's comparable to the binsearch/bloom approach, and in
some cases it actually beats them quite significantly. It seems to
depend on how expensive the comparison is - for "int" the comparison is
very cheap and there's almost no difference. For "text" the comparison
is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements and 10%
match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms plus
some overhead to build and check the bits. The hash join probably
eliminates a lot of the remaining comparisons, because the hash table
is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom filter
implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth the
effort - I agree that seems quite complex. But perhaps this suggest we
should not be doing binary search and instead just build a simple hash
table - that seems much simpler, and it'll probably give us about the
same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not all
that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem. We
don't care about it now, and it's not clear to me what we could do about
it - we already have the array in memory anyway, so it's a bit futile.
Furthermore, if we need to care about it, it probably applies to the
binary search too.

I was looking earlier at what our standard hash implementation was,
and it seemed less obvious what was needed to set that up (so binary
search seemed a faster proof of concept). If you happen to have any
pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It has to
care about work_mem, so it implements batching, etc. That's a lot of
complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking that the
benchmarks makes sense and running it independently. I'm not aware of
any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

I've attached a patch series as before, but with an additional patch
that switches to using dynahash instead of binary search.

OK. I can't take closer look at the moment, I'll check later.

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

No particular reason; it wasn't clear to me that there was a reason to
prefer one or the other (and I'm not acquainted with the codebase
enough to know the differences), so I chose dynahash because it was
easier to find examples to follow for implementation.

OK, understood.

Whereas before the benchmarking ended up with a trimodal distribution
(i.e., master with IN <list>, patch with IN <list>, and either with IN
VALUES), the hash implementation brings us back to an effectively
bimodal distribution -- though the hash scalar array op expression
implementation for text is about 5% faster than the hash join.

Nice. I'm not surprised this is a bit faster than hash join, which has
to worry about additional stuff.

Current outstanding thoughts (besides comment/naming cleanup):

- The saop costing needs to be updated to match, as Tomas pointed out.

- Should we be concerned about single execution cases? For example, is
the regression of speed on a simple SELECT x IN something we should
try to defeat by only kicking in the optimization if we execute in a
loop at least twice? That might be of particular interest to pl/pgsql.

I don't follow. How is this related to the number of executions and/or
plpgsql? I suspect you might be talking about prepared statements, but
surely the hash table is built for each execution anyway, even if the
plan is reused, right?

I think the issue we've been talking about is considering the number of
lookups we expect to do in the array/hash table. But that has nothing to
do with plpgsql and/or multiple executions ...

Suppose I do "SELECT 1 IN <100 item list>" (whether just as a
standalone query or in pl/pgsql). Then it doesn't make sense to use
the optimization, because it can't possibly win over a naive linear
scan through the array since to build the hash we have to do that
linear scan anyway (I suppose in theory the hashing function could be
dramatically faster than the equality op, so maybe it could win
overall, but it seems unlikely to me).

True. I'm sure we could construct such cases, but this is hardly the
only place where that would be an issue. We could create some rough
costing model and make a decision based on that.

I'm not so concerned about this in any query where we have a real FROM
clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know for
certain (correct me if I've wrong on this) that we'll only execute the
expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and I
see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we can
disable this optimization for now.

- Should we have a test for an operator with a non-strict function?
I'm not aware of any built-in ops that have that characteristic;
would you suggest just creating a fake one for the test?

Dunno, I haven't thought about this very much. In general I think the
new code should simply behave the same as the current code, i.e. if
it does not check for strictness, we don't need either.

Both the original implementation and this optimized version have the
following short circuit condition:

if (fcinfo->args[0].isnull && strictfunc)

But I'm not sure there are any existing tests to show that the "&&
strictfunc" is required.

Ah! You mean a regression test, not a test in the "if condition" sense.
I don't see a reason not to have such test, although it's probably not
something we should require from this patch.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#26Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tomas Vondra (#25)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

út 28. 4. 2020 v 15:26 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:

On Tue, Apr 28, 2020 at 08:39:18AM -0400, James Coleman wrote:

On Tue, Apr 28, 2020 at 8:25 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Mon, Apr 27, 2020 at 09:04:09PM -0400, James Coleman wrote:

On Sun, Apr 26, 2020 at 7:41 PM James Coleman <jtc331@gmail.com>

wrote:

On Sun, Apr 26, 2020 at 4:49 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sun, Apr 26, 2020 at 02:46:19PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 8:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Sat, Apr 25, 2020 at 06:47:41PM -0400, James Coleman wrote:

On Sat, Apr 25, 2020 at 5:41 PM David Rowley <

dgrowleyml@gmail.com> wrote:

On Sun, 26 Apr 2020 at 00:40, Tomas Vondra <

tomas.vondra@2ndquadrant.com> wrote:

This reminds me our attempts to add bloom filters to hash

joins, which I

think ran into mostly the same challenge of deciding when

the bloom

filter can be useful and is worth the extra work.

Speaking of that, it would be interesting to see how a test

where you

write the query as IN(VALUES(...)) instead of IN() compares.

It would

be interesting to know if the planner is able to make a more

suitable

choice and also to see how all the work over the years to

improve Hash

Joins compares to the bsearch with and without the bloom

filter.

It would be interesting.

It also makes one wonder about optimizing these into to hash
joins...which I'd thought about over at [1]. I think it'd be a

very

significant effort though.

I modified the script to also do the join version of the query.

I can

only run it on my laptop at the moment, so the results may be a

bit

different from those I shared before, but it's interesting I

think.

In most cases it's comparable to the binsearch/bloom approach,

and in

some cases it actually beats them quite significantly. It seems

to

depend on how expensive the comparison is - for "int" the

comparison is

very cheap and there's almost no difference. For "text" the

comparison

is much more expensive, and there are significant speedups.

For example the test with 100k lookups in array of 10k elements

and 10%

match probability, the timings are these

master: 62362 ms
binsearch: 201 ms
bloom: 65 ms
hashjoin: 36 ms

I do think the explanation is fairly simple - the bloom filter
eliminates about 90% of the expensive comparisons, so it's 20ms

plus

some overhead to build and check the bits. The hash join

probably

eliminates a lot of the remaining comparisons, because the hash

table

is sized to have one tuple per bucket.

Note: I also don't claim the PoC has the most efficient bloom

filter

implementation possible. I'm sure it could be made faster.

Anyway, I'm not sure transforming this to a hash join is worth

the

effort - I agree that seems quite complex. But perhaps this

suggest we

should not be doing binary search and instead just build a

simple hash

table - that seems much simpler, and it'll probably give us

about the

same benefits.

That's actually what I originally thought about doing, but I chose
binary search since it seemed a lot easier to get off the ground.

OK, that makes perfect sense.

If we instead build a hash is there anything else we need to be
concerned about? For example, work mem? I suppose for the binary
search we already have to expand the array, so perhaps it's not

all

that meaningful relative to that...

I don't think we need to be particularly concerned about work_mem.

We

don't care about it now, and it's not clear to me what we could do

about

it - we already have the array in memory anyway, so it's a bit

futile.

Furthermore, if we need to care about it, it probably applies to

the

binary search too.

I was looking earlier at what our standard hash implementation

was,

and it seemed less obvious what was needed to set that up (so

binary

search seemed a faster proof of concept). If you happen to have

any

pointers to similar usages I should look at, please let me know.

I think the hash join implementation is far too complicated. It

has to

care about work_mem, so it implements batching, etc. That's a lot

of

complexity we don't need here. IMO we could use either the usual
dynahash, or maybe even the simpler simplehash.

FWIW it'd be good to verify the numbers I shared, i.e. checking

that the

benchmarks makes sense and running it independently. I'm not aware

of

any issues but it was done late at night and only ran on my laptop.

Some quick calculations (don't have the scripting in a form I can
attach yet; using this as an opportunity to hack on a genericized
performance testing framework of sorts) suggest your results are
correct. I was also testing on my laptop, but I showed 1.) roughly
equivalent results for IN (VALUES ...) and IN (<list>) for integers,
but when I switch to (short; average 3 characters long) text values I
show the hash join on VALUES is twice as fast as the binary search.

Given that, I'm planning to implement this as a hash lookup and share
a revised patch.

I've attached a patch series as before, but with an additional patch
that switches to using dynahash instead of binary search.

OK. I can't take closer look at the moment, I'll check later.

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

No particular reason; it wasn't clear to me that there was a reason to
prefer one or the other (and I'm not acquainted with the codebase
enough to know the differences), so I chose dynahash because it was
easier to find examples to follow for implementation.

OK, understood.

Whereas before the benchmarking ended up with a trimodal distribution
(i.e., master with IN <list>, patch with IN <list>, and either with IN
VALUES), the hash implementation brings us back to an effectively
bimodal distribution -- though the hash scalar array op expression
implementation for text is about 5% faster than the hash join.

Nice. I'm not surprised this is a bit faster than hash join, which has
to worry about additional stuff.

Current outstanding thoughts (besides comment/naming cleanup):

- The saop costing needs to be updated to match, as Tomas pointed out.

- Should we be concerned about single execution cases? For example, is
the regression of speed on a simple SELECT x IN something we should
try to defeat by only kicking in the optimization if we execute in a
loop at least twice? That might be of particular interest to pl/pgsql.

I don't follow. How is this related to the number of executions and/or
plpgsql? I suspect you might be talking about prepared statements, but
surely the hash table is built for each execution anyway, even if the
plan is reused, right?

I think the issue we've been talking about is considering the number of
lookups we expect to do in the array/hash table. But that has nothing to
do with plpgsql and/or multiple executions ...

Suppose I do "SELECT 1 IN <100 item list>" (whether just as a
standalone query or in pl/pgsql). Then it doesn't make sense to use
the optimization, because it can't possibly win over a naive linear
scan through the array since to build the hash we have to do that
linear scan anyway (I suppose in theory the hashing function could be
dramatically faster than the equality op, so maybe it could win
overall, but it seems unlikely to me).

True. I'm sure we could construct such cases, but this is hardly the
only place where that would be an issue. We could create some rough
costing model and make a decision based on that.

I'm not so concerned about this in any query where we have a real FROM
clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know for
certain (correct me if I've wrong on this) that we'll only execute the
expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and I
see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we can
disable this optimization for now.

a := a + 1

is translated to

SELECT $1 + 1 and save result to var a

The queries like this "SELECT $1 + 1" are simple expressions. They are
evaluated just on executor level - it skip SPI

the simple expression has not FROM clause, and have to return just one row.
I am not sure if it is required, it has to return just one column.

I am not sure if executor knows so expression is executed as simply
expressions. But probably it can be deduced from context

Pavel

Show quoted text

- Should we have a test for an operator with a non-strict function?
I'm not aware of any built-in ops that have that characteristic;
would you suggest just creating a fake one for the test?

Dunno, I haven't thought about this very much. In general I think the
new code should simply behave the same as the current code, i.e. if
it does not check for strictness, we don't need either.

Both the original implementation and this optimized version have the
following short circuit condition:

if (fcinfo->args[0].isnull && strictfunc)

But I'm not sure there are any existing tests to show that the "&&
strictfunc" is required.

Ah! You mean a regression test, not a test in the "if condition" sense.
I don't see a reason not to have such test, although it's probably not
something we should require from this patch.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#27Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Pavel Stehule (#26)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 03:43:43PM +0200, Pavel Stehule wrote:

�t 28. 4. 2020 v 15:26 odes�latel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:

...

I'm not so concerned about this in any query where we have a real FROM
clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know for
certain (correct me if I've wrong on this) that we'll only execute the
expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and I
see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we can
disable this optimization for now.

a := a + 1

is translated to

SELECT $1 + 1 and save result to var a

The queries like this "SELECT $1 + 1" are simple expressions. They are
evaluated just on executor level - it skip SPI

the simple expression has not FROM clause, and have to return just one row.
I am not sure if it is required, it has to return just one column.

I am not sure if executor knows so expression is executed as simply
expressions. But probably it can be deduced from context

Not sure. The executor state is created by exec_eval_simple_expr, which
calls ExecInitExprWithParams (and it's the only caller). And that in
turn is the only place that leaves (state->parent == NULL). So maybe
that's a way to identify simple (standalone) expressions? Otherwise we
might add a new EEO_FLAG_* to identify these expressions explicitly.

I wonder if it would be possible to identify cases when the expression
is executed in a loop, e.g. like this:

FOR i IN 1..1000 LOOP
x := y IN (1, 2, ..., 999);
END LOOP;

in which case we only build the ScalarArrayOpExpr once, so maybe we
could keep the hash table for all executions. But maybe that's not
possible or maybe it's pointless for other reasons. It sure looks a bit
like trying to build a query engine from FOR loop.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tomas Vondra (#27)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

út 28. 4. 2020 v 16:48 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:

On Tue, Apr 28, 2020 at 03:43:43PM +0200, Pavel Stehule wrote:

út 28. 4. 2020 v 15:26 odesílatel Tomas Vondra <

tomas.vondra@2ndquadrant.com>

napsal:

...

I'm not so concerned about this in any query where we have a real FROM
clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know for
certain (correct me if I've wrong on this) that we'll only execute the
expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and I
see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we can
disable this optimization for now.

a := a + 1

is translated to

SELECT $1 + 1 and save result to var a

The queries like this "SELECT $1 + 1" are simple expressions. They are
evaluated just on executor level - it skip SPI

the simple expression has not FROM clause, and have to return just one

row.

I am not sure if it is required, it has to return just one column.

I am not sure if executor knows so expression is executed as simply
expressions. But probably it can be deduced from context

Not sure. The executor state is created by exec_eval_simple_expr, which
calls ExecInitExprWithParams (and it's the only caller). And that in
turn is the only place that leaves (state->parent == NULL). So maybe
that's a way to identify simple (standalone) expressions? Otherwise we
might add a new EEO_FLAG_* to identify these expressions explicitly.

I wonder if it would be possible to identify cases when the expression
is executed in a loop, e.g. like this:

FOR i IN 1..1000 LOOP
x := y IN (1, 2, ..., 999);
END LOOP;

in which case we only build the ScalarArrayOpExpr once, so maybe we
could keep the hash table for all executions. But maybe that's not
possible or maybe it's pointless for other reasons. It sure looks a bit
like trying to build a query engine from FOR loop.

Theoretically it is possible, not now. But I don't think so it is
necessary. I cannot to remember this pattern in any plpgsql code and I
never seen any request on this feature.

I don't think so this is task for plpgsql engine. Maybe for JIT sometimes.

Show quoted text

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#29James Coleman
jtc331@gmail.com
In reply to: Pavel Stehule (#28)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 11:18 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

út 28. 4. 2020 v 16:48 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com> napsal:

On Tue, Apr 28, 2020 at 03:43:43PM +0200, Pavel Stehule wrote:

út 28. 4. 2020 v 15:26 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:

...

I'm not so concerned about this in any query where we have a real FROM
clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know for
certain (correct me if I've wrong on this) that we'll only execute the
expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and I
see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we can
disable this optimization for now.

a := a + 1

is translated to

SELECT $1 + 1 and save result to var a

The queries like this "SELECT $1 + 1" are simple expressions. They are
evaluated just on executor level - it skip SPI

the simple expression has not FROM clause, and have to return just one row.
I am not sure if it is required, it has to return just one column.

Yes, this is what I meant by simple expressions.

I am not sure if executor knows so expression is executed as simply
expressions. But probably it can be deduced from context

Not sure. The executor state is created by exec_eval_simple_expr, which
calls ExecInitExprWithParams (and it's the only caller). And that in
turn is the only place that leaves (state->parent == NULL). So maybe
that's a way to identify simple (standalone) expressions? Otherwise we
might add a new EEO_FLAG_* to identify these expressions explicitly.

I'll look into doing one of these.

I wonder if it would be possible to identify cases when the expression
is executed in a loop, e.g. like this:

FOR i IN 1..1000 LOOP
x := y IN (1, 2, ..., 999);
END LOOP;

in which case we only build the ScalarArrayOpExpr once, so maybe we
could keep the hash table for all executions. But maybe that's not
possible or maybe it's pointless for other reasons. It sure looks a bit
like trying to build a query engine from FOR loop.

Theoretically it is possible, not now. But I don't think so it is necessary. I cannot to remember this pattern in any plpgsql code and I never seen any request on this feature.

I don't think so this is task for plpgsql engine. Maybe for JIT sometimes.

Agreed. I'd thought about this kind of scenario when I brought this
up, but I think solving it would the responsibility of the pg/pgsql
compiler rather than the expression evaluation code, because it'd have
to recognize the situation and setup a shared expression evaluation
context to be reused each time through the loop.

James

#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: James Coleman (#29)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

út 28. 4. 2020 v 18:17 odesílatel James Coleman <jtc331@gmail.com> napsal:

On Tue, Apr 28, 2020 at 11:18 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

út 28. 4. 2020 v 16:48 odesílatel Tomas Vondra <

tomas.vondra@2ndquadrant.com> napsal:

On Tue, Apr 28, 2020 at 03:43:43PM +0200, Pavel Stehule wrote:

út 28. 4. 2020 v 15:26 odesílatel Tomas Vondra <

tomas.vondra@2ndquadrant.com>

napsal:

...

I'm not so concerned about this in any query where we have a real

FROM

clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know

for

certain (correct me if I've wrong on this) that we'll only execute

the

expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and

I

see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we

can

disable this optimization for now.

a := a + 1

is translated to

SELECT $1 + 1 and save result to var a

The queries like this "SELECT $1 + 1" are simple expressions. They are
evaluated just on executor level - it skip SPI

the simple expression has not FROM clause, and have to return just one

row.

I am not sure if it is required, it has to return just one column.

Yes, this is what I meant by simple expressions.

I am not sure if executor knows so expression is executed as simply
expressions. But probably it can be deduced from context

Not sure. The executor state is created by exec_eval_simple_expr, which
calls ExecInitExprWithParams (and it's the only caller). And that in
turn is the only place that leaves (state->parent == NULL). So maybe
that's a way to identify simple (standalone) expressions? Otherwise we
might add a new EEO_FLAG_* to identify these expressions explicitly.

I'll look into doing one of these.

I wonder if it would be possible to identify cases when the expression
is executed in a loop, e.g. like this:

FOR i IN 1..1000 LOOP
x := y IN (1, 2, ..., 999);
END LOOP;

in which case we only build the ScalarArrayOpExpr once, so maybe we
could keep the hash table for all executions. But maybe that's not
possible or maybe it's pointless for other reasons. It sure looks a bit
like trying to build a query engine from FOR loop.

Theoretically it is possible, not now. But I don't think so it is

necessary. I cannot to remember this pattern in any plpgsql code and I
never seen any request on this feature.

I don't think so this is task for plpgsql engine. Maybe for JIT

sometimes.

Agreed. I'd thought about this kind of scenario when I brought this
up, but I think solving it would the responsibility of the pg/pgsql
compiler rather than the expression evaluation code, because it'd have
to recognize the situation and setup a shared expression evaluation
context to be reused each time through the loop.

can be nice if new implementation was not slower then older in all
environments and context (including plpgsql expressions)

Regards

Pavel

Show quoted text

James

#31James Coleman
jtc331@gmail.com
In reply to: Pavel Stehule (#30)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 1:40 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:

út 28. 4. 2020 v 18:17 odesílatel James Coleman <jtc331@gmail.com> napsal:

On Tue, Apr 28, 2020 at 11:18 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

út 28. 4. 2020 v 16:48 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com> napsal:

On Tue, Apr 28, 2020 at 03:43:43PM +0200, Pavel Stehule wrote:

út 28. 4. 2020 v 15:26 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:

...

I'm not so concerned about this in any query where we have a real FROM
clause because even if we end up with only one row, the relative
penalty is low, and the potential gain is very high. But simple
expressions in pl/pgsql, for example, are a case where we can know for
certain (correct me if I've wrong on this) that we'll only execute the
expression once, which means there's probably always a penalty for
choosing the implementation with setup costs over the default linear
scan through the array.

What do you mean by "simple expressions"? I'm not plpgsql expert and I
see it mostly as a way to glue together SQL queries, but yeah - if we
know a given ScalarArrayOpExpr will only be executed once, then we can
disable this optimization for now.

a := a + 1

is translated to

SELECT $1 + 1 and save result to var a

The queries like this "SELECT $1 + 1" are simple expressions. They are
evaluated just on executor level - it skip SPI

the simple expression has not FROM clause, and have to return just one row.
I am not sure if it is required, it has to return just one column.

Yes, this is what I meant by simple expressions.

I am not sure if executor knows so expression is executed as simply
expressions. But probably it can be deduced from context

Not sure. The executor state is created by exec_eval_simple_expr, which
calls ExecInitExprWithParams (and it's the only caller). And that in
turn is the only place that leaves (state->parent == NULL). So maybe
that's a way to identify simple (standalone) expressions? Otherwise we
might add a new EEO_FLAG_* to identify these expressions explicitly.

I'll look into doing one of these.

I wonder if it would be possible to identify cases when the expression
is executed in a loop, e.g. like this:

FOR i IN 1..1000 LOOP
x := y IN (1, 2, ..., 999);
END LOOP;

in which case we only build the ScalarArrayOpExpr once, so maybe we
could keep the hash table for all executions. But maybe that's not
possible or maybe it's pointless for other reasons. It sure looks a bit
like trying to build a query engine from FOR loop.

Theoretically it is possible, not now. But I don't think so it is necessary. I cannot to remember this pattern in any plpgsql code and I never seen any request on this feature.

I don't think so this is task for plpgsql engine. Maybe for JIT sometimes.

Agreed. I'd thought about this kind of scenario when I brought this
up, but I think solving it would the responsibility of the pg/pgsql
compiler rather than the expression evaluation code, because it'd have
to recognize the situation and setup a shared expression evaluation
context to be reused each time through the loop.

can be nice if new implementation was not slower then older in all environments and context (including plpgsql expressions)

Agreed, which is why I'm going to look into preventing using the new
code path for simple expressions.

James

#32James Coleman
jtc331@gmail.com
In reply to: James Coleman (#24)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.

On Tue, Apr 28, 2020 at 8:39 AM James Coleman <jtc331@gmail.com> wrote:
...

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

No particular reason; it wasn't clear to me that there was a reason to
prefer one or the other (and I'm not acquainted with the codebase
enough to know the differences), so I chose dynahash because it was
easier to find examples to follow for implementation.

Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

James

#33Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#32)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 06:22:20PM -0400, James Coleman wrote:

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.

On Tue, Apr 28, 2020 at 8:39 AM James Coleman <jtc331@gmail.com> wrote:
...

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

No particular reason; it wasn't clear to me that there was a reason to
prefer one or the other (and I'm not acquainted with the codebase
enough to know the differences), so I chose dynahash because it was
easier to find examples to follow for implementation.

Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

I recall doing some benchmarks for that patch, but it's so long I don't
really remember the details. But in general, I agree simplehash is a bit
more efficient in terms of CPU / caching.

I think the changes required to switch from dynahash to simplehash are
fairly small, so I think the best thing we can do is just try do some
measurement and then decide.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

I wouldn't object to that. Although maybe we should simply add some
basic recommendations to the comments in dynahash/simplehash.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#34Andres Freund
andres@anarazel.de
In reply to: James Coleman (#32)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

Hi,

On 2020-04-28 18:22:20 -0400, James Coleman wrote:

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.
[...]
Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Benefits of dynahash (chained hashtable):
- supports partitioning, useful for shared memory accessed under locks
- better performance for large entries, as they don't need to be moved
around in case of hash conflicts
- stable pointers to hash table entries

Benefits of simplehash (open addressing hash table):
- no indirect function calls, known structure sizes, due to "templated"
code generation (these show up substantially in profiles for dynahash)
- considerably faster for small entries due to previous point, and due
open addressing hash tables having better cache behaviour than chained
hashtables
- once set-up the interface is type safe and easier to use
- no overhead of a separate memory context etc

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

Seems plausible to me.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

That'd make sense to me.

Greetings,

Andres Freund

#35James Coleman
jtc331@gmail.com
In reply to: Andres Freund (#34)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 7:05 PM Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2020-04-28 18:22:20 -0400, James Coleman wrote:

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.
[...]
Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Benefits of dynahash (chained hashtable):
- supports partitioning, useful for shared memory accessed under locks
- better performance for large entries, as they don't need to be moved
around in case of hash conflicts
- stable pointers to hash table entries

Benefits of simplehash (open addressing hash table):
- no indirect function calls, known structure sizes, due to "templated"
code generation (these show up substantially in profiles for dynahash)
- considerably faster for small entries due to previous point, and due
open addressing hash tables having better cache behaviour than chained
hashtables
- once set-up the interface is type safe and easier to use
- no overhead of a separate memory context etc

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

Seems plausible to me.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

That'd make sense to me.

Cool, I'll work on that as I have time then.

One question: what is the reasoning behind having SH_STORE_HASH? The
only things I could imagine would be a case where you have external
pointers to some set of values or need to be able to use the hash for
other reasons besides the hash table (and so can avoid calculating it
twice), but maybe I'm missing something.

Thanks,
James

#36Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#35)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Wed, Apr 29, 2020 at 10:26:12AM -0400, James Coleman wrote:

On Tue, Apr 28, 2020 at 7:05 PM Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2020-04-28 18:22:20 -0400, James Coleman wrote:

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.
[...]
Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Benefits of dynahash (chained hashtable):
- supports partitioning, useful for shared memory accessed under locks
- better performance for large entries, as they don't need to be moved
around in case of hash conflicts
- stable pointers to hash table entries

Benefits of simplehash (open addressing hash table):
- no indirect function calls, known structure sizes, due to "templated"
code generation (these show up substantially in profiles for dynahash)
- considerably faster for small entries due to previous point, and due
open addressing hash tables having better cache behaviour than chained
hashtables
- once set-up the interface is type safe and easier to use
- no overhead of a separate memory context etc

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

Seems plausible to me.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

That'd make sense to me.

Cool, I'll work on that as I have time then.

One question: what is the reasoning behind having SH_STORE_HASH? The
only things I could imagine would be a case where you have external
pointers to some set of values or need to be able to use the hash for
other reasons besides the hash table (and so can avoid calculating it
twice), but maybe I'm missing something.

I believe it's because computing the hash may be fairly expensive for
some data types, in which case it may be better to just store it for
future use.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#37James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#36)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Wed, Apr 29, 2020 at 11:17 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Wed, Apr 29, 2020 at 10:26:12AM -0400, James Coleman wrote:

On Tue, Apr 28, 2020 at 7:05 PM Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2020-04-28 18:22:20 -0400, James Coleman wrote:

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.
[...]
Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Benefits of dynahash (chained hashtable):
- supports partitioning, useful for shared memory accessed under locks
- better performance for large entries, as they don't need to be moved
around in case of hash conflicts
- stable pointers to hash table entries

Benefits of simplehash (open addressing hash table):
- no indirect function calls, known structure sizes, due to "templated"
code generation (these show up substantially in profiles for dynahash)
- considerably faster for small entries due to previous point, and due
open addressing hash tables having better cache behaviour than chained
hashtables
- once set-up the interface is type safe and easier to use
- no overhead of a separate memory context etc

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

Seems plausible to me.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

That'd make sense to me.

Cool, I'll work on that as I have time then.

One question: what is the reasoning behind having SH_STORE_HASH? The
only things I could imagine would be a case where you have external
pointers to some set of values or need to be able to use the hash for
other reasons besides the hash table (and so can avoid calculating it
twice), but maybe I'm missing something.

I believe it's because computing the hash may be fairly expensive for
some data types, in which case it may be better to just store it for
future use.

But is it storing it for use primarily by the hash table
implementation (i.e., does it need the hash stored this way to avoid
repeated recalculation) or for caller's use?

James

#38Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: James Coleman (#37)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Wed, Apr 29, 2020 at 11:34:24AM -0400, James Coleman wrote:

On Wed, Apr 29, 2020 at 11:17 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

On Wed, Apr 29, 2020 at 10:26:12AM -0400, James Coleman wrote:

On Tue, Apr 28, 2020 at 7:05 PM Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2020-04-28 18:22:20 -0400, James Coleman wrote:

I cc'd Andres given his commit introduced simplehash, so I figured
he'd probably have a few pointers on when each one might be useful.
[...]
Do you have any thoughts on what the trade-offs/use-cases etc. are for
dynahash versus simple hash?

From reading the commit message in b30d3ea824c it seems like simple
hash is faster and optimized for CPU cache benefits. The comments at
the top of simplehash.h also discourage it's use in non
performance/space sensitive uses, but there isn't anything I can see
that explicitly tries to discuss when dynahash is useful, etc.

Benefits of dynahash (chained hashtable):
- supports partitioning, useful for shared memory accessed under locks
- better performance for large entries, as they don't need to be moved
around in case of hash conflicts
- stable pointers to hash table entries

Benefits of simplehash (open addressing hash table):
- no indirect function calls, known structure sizes, due to "templated"
code generation (these show up substantially in profiles for dynahash)
- considerably faster for small entries due to previous point, and due
open addressing hash tables having better cache behaviour than chained
hashtables
- once set-up the interface is type safe and easier to use
- no overhead of a separate memory context etc

Given the performance notes in that commit message, I thinking
switching to simple hash is worth it.

Seems plausible to me.

But I also wonder if there might be some value in a README or comments
addition that would be a guide to what the various hash
implementations are useful for. If there's interest, I could try to
type something short up so that we have something to make the code
base a bit more discoverable.

That'd make sense to me.

Cool, I'll work on that as I have time then.

One question: what is the reasoning behind having SH_STORE_HASH? The
only things I could imagine would be a case where you have external
pointers to some set of values or need to be able to use the hash for
other reasons besides the hash table (and so can avoid calculating it
twice), but maybe I'm missing something.

I believe it's because computing the hash may be fairly expensive for
some data types, in which case it may be better to just store it for
future use.

But is it storing it for use primarily by the hash table
implementation (i.e., does it need the hash stored this way to avoid
repeated recalculation) or for caller's use?

For the hash table implementation, I think. Simplehash is using "robin
hood" hashing, which needs to decide which entry to move in case of
collision. AFAIC that requires the knowledge of hash value for both the
new and existing entry, and having to compute that over and over would
be fairly expensive. But that's my understanding, I might be wrong and
it's useful for external use too.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#39James Coleman
jtc331@gmail.com
In reply to: Tomas Vondra (#23)
3 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Apr 28, 2020 at 8:25 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
...

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

I've attached a patch series that includes switching to simplehash.
Obviously we'd really just collapse all of these patches, but it's
perhaps interesting to see the changes required to use each style
(binary search, dynahash, simplehash).

As before, there are clearly comments and naming things to be
addressed, but the implementation should be reasonably clean.

James

Attachments:

v3-0003-Try-simple-hash.patchtext/x-patch; charset=US-ASCII; name=v3-0003-Try-simple-hash.patchDownload
From d455a57701df064f96e5f2a3be2b1c736bacc485 Mon Sep 17 00:00:00 2001
From: James Coleman <jtc331@gmail.com>
Date: Tue, 28 Apr 2020 21:33:12 -0400
Subject: [PATCH v3 3/3] Try simple hash

---
 src/backend/executor/execExpr.c       | 24 +++----
 src/backend/executor/execExprInterp.c | 99 +++++++++++++++++----------
 src/include/executor/execExpr.h       | 26 ++++++-
 3 files changed, 100 insertions(+), 49 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 6249db5426..37c1669153 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -950,13 +950,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
 				Oid			func;
-				Oid			hash_func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
-				FmgrInfo   *hash_finfo;
-				FunctionCallInfo hash_fcinfo;
 				AclResult	aclresult;
 				bool		useBinarySearch = false;
 
@@ -996,18 +993,20 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
 					{
+						Oid			hash_func;
+
 						/*
-						 * Find the hash op that matches the originally
-						 * planned equality op.
+						 * Find the hash op that matches the originally planned
+						 * equality op. If we don't have one, we'll just fall
+						 * back to the default linear scan implementation.
 						 */
 						useBinarySearch = get_op_hash_functions(opexpr->opno, NULL, &hash_func);
 
-						/*
-						 * But we may not have one, so fall back to the
-						 * default implementation if necessary.
-						 */
 						if (useBinarySearch)
 						{
+							FmgrInfo   *hash_finfo;
+							FunctionCallInfo hash_fcinfo;
+
 							hash_finfo = palloc0(sizeof(FmgrInfo));
 							hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
 							fmgr_info(hash_func, hash_finfo);
@@ -1015,6 +1014,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
 							InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
 													 opexpr->inputcollid, NULL, NULL);
 							InvokeFunctionExecuteHook(hash_func);
+
+							scratch.d.scalararraybinsearchop.hash_finfo = hash_finfo;
+							scratch.d.scalararraybinsearchop.hash_fcinfo_data = hash_fcinfo;
+							scratch.d.scalararraybinsearchop.hash_fn_addr = hash_finfo->fn_addr;
 						}
 					}
 				}
@@ -1044,9 +1047,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					scratch.d.scalararraybinsearchop.finfo = finfo;
 					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
 					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
-					scratch.d.scalararraybinsearchop.hash_finfo = hash_finfo;
-					scratch.d.scalararraybinsearchop.hash_fcinfo_data = hash_fcinfo;
-					scratch.d.scalararraybinsearchop.hash_fn_addr = hash_finfo->fn_addr;
 				}
 				else
 				{
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index e54e807c6b..aa7f5ae0df 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -178,6 +178,27 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
+static bool
+element_match(struct saophash_hash *tb, Datum key1, Datum key2);
+static uint32 element_hash(struct saophash_hash *tb, Datum key);
+
+/*
+ * Define parameters for ScalarArrayOpExpr hash table code generation. The interface is
+ * *also* declared in execnodes.h (to generate the types, which are externally
+ * visible).
+ */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
+#define SH_KEY_TYPE Datum
+#define SH_KEY key
+#define SH_HASH_KEY(tb, key) element_hash(tb, key)
+#define SH_EQUAL(tb, a, b) element_match(tb, a, b)
+#define SH_SCOPE extern
+#define SH_STORE_HASH
+#define SH_GET_HASH(tb, a) a->hash
+#define SH_DEFINE
+#include "lib/simplehash.h"
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -3591,8 +3612,6 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
-static ExprEvalStep *current_saop_op;
-
 /*
  * Hash function for elements.
  *
@@ -3601,16 +3620,16 @@ static ExprEvalStep *current_saop_op;
  */
 /* XXX: Name function to be specific to saop binsearch? */
 static uint32
-element_hash(const void *key, Size keysize)
+element_hash(struct saophash_hash *tb, Datum key)
 {
+	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.scalararraybinsearchop.hash_fcinfo_data;
 	Datum hash;
-	FunctionCallInfo fcinfo = current_saop_op->d.scalararraybinsearchop.hash_fcinfo_data;
 
-	fcinfo->args[0].value = *((const Datum *) key);
+	fcinfo->args[0].value = key;
 	fcinfo->args[0].isnull = false;
 
-	/* The keysize parameter is superfluous here */
-	hash = current_saop_op->d.scalararraybinsearchop.hash_fn_addr(fcinfo);
+	hash = elements_tab->op->d.scalararraybinsearchop.hash_fn_addr(fcinfo);
 
 	return DatumGetUInt32(hash);
 }
@@ -3619,21 +3638,22 @@ element_hash(const void *key, Size keysize)
  * Matching function for elements, to be used in hashtable lookups.
  */
 /* XXX: Name function to be specific to saop binsearch? */
-static int
-element_match(const void *key1, const void *key2, Size keysize)
+static bool
+element_match(struct saophash_hash *tb, Datum key1, Datum key2)
 {
 	Datum result;
-	FunctionCallInfo fcinfo = current_saop_op->d.scalararraybinsearchop.fcinfo_data;
 
-	fcinfo->args[0].value = *((const Datum *) key1);
+	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = key1;
 	fcinfo->args[0].isnull = false;
-	fcinfo->args[1].value = *((const Datum *) key2);
+	fcinfo->args[1].value = key2;
 	fcinfo->args[1].isnull = false;
 
-	/* The keysize parameter is superfluous here */
-	result = current_saop_op->d.scalararraybinsearchop.fn_addr(fcinfo);
+	result = elements_tab->op->d.scalararraybinsearchop.fn_addr(fcinfo);
 
-	return DatumGetBool(result) ? 0 : 1;
+	return DatumGetBool(result);
 }
 
 /*
@@ -3653,22 +3673,21 @@ element_match(const void *key1, const void *key2, Size keysize)
 void
 ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
+	ScalarArrayOpExprHashTable elements_tab = op->d.scalararraybinsearchop.elements_tab;
 	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
 	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
 	ArrayType  *arr;
 	Datum		scalar = fcinfo->args[0].value;
+	bool		scalar_isnull = fcinfo->args[0].isnull;
 	Datum		result;
 	bool		resultnull;
 	bool		hashfound;
-	int			res;
 
 	/* If we're only executing once, do we need a way to fall back to the regular loop? */
 
 	/* We don't setup a binary search op if the array const is null. */
 	Assert(!*op->resnull);
 
-	current_saop_op = op;
-
 	/*
 	 * If the scalar is NULL, and the function is strict, return NULL; no
 	 * point in executing the search.
@@ -3680,17 +3699,17 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	}
 
 	/* Preprocess the array the first time we execute the op. */
-	if (op->d.scalararraybinsearchop.elements_tab == NULL)
+	if (elements_tab == NULL)
 	{
 		int16		typlen;
 		bool		typbyval;
 		char		typalign;
-		HASHCTL		elem_hash_ctl;
 		int			nitems;
 		int			num_nulls = 0;
 		char	   *s;
 		bits8	   *bitmap;
 		int			bitmask;
+		MemoryContext oldcontext;
 
 		arr = DatumGetArrayTypeP(*op->resvalue);
 		nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
@@ -3700,16 +3719,14 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 							 &typbyval,
 							 &typalign);
 
-		MemSet(&elem_hash_ctl, 0, sizeof(elem_hash_ctl));
-		elem_hash_ctl.keysize = sizeof(Datum);
-		elem_hash_ctl.entrysize = sizeof(Datum);
-		elem_hash_ctl.hash = element_hash;
-		elem_hash_ctl.match = element_match;
-		elem_hash_ctl.hcxt = econtext->ecxt_per_query_memory;
-		op->d.scalararraybinsearchop.elements_tab = hash_create("Scalar array op expr elements",
-								   nitems,
-								   &elem_hash_ctl,
-								   HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+		elements_tab = (ScalarArrayOpExprHashTable) palloc(sizeof(ScalarArrayOpExprHashTableData));
+		op->d.scalararraybinsearchop.elements_tab = elements_tab;
+		elements_tab->op = op;
+		elements_tab->hashtab = saophash_create(CurrentMemoryContext, nitems, elements_tab);
+
+		MemoryContextSwitchTo(oldcontext);
 
 		s = (char *) ARR_DATA_PTR(arr);
 		bitmap = ARR_NULLBITMAP(arr);
@@ -3729,7 +3746,7 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 				s = att_addlength_pointer(s, typlen, s);
 				s = (char *) att_align_nominal(s, typalign);
 
-				hash_search(op->d.scalararraybinsearchop.elements_tab, (const void *) &element, HASH_ENTER, NULL);
+				saophash_insert(elements_tab->hashtab, element, &hashfound);
 			}
 
 			/* Advance bitmap pointer if any. */
@@ -3758,7 +3775,8 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	}
 
 	/* Check the hash to see if we have a match. */
-	hash_search(op->d.scalararraybinsearchop.elements_tab, (const void *) &scalar, HASH_FIND, &hashfound);
+	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
+
 	result = BoolGetDatum(hashfound);
 	resultnull = false;
 
@@ -3771,18 +3789,27 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	{
 		if (strictfunc)
 		{
-			/* Had nulls, so strict function implies null. */
+			/* Had nulls and is a strict function, so instead of executing the
+			 * function onces with a null rhs, we can assume null. */
 			result = (Datum) 0;
 			resultnull = true;
 		}
 		else
 		{
-			/* Execute function will null rhs just once. */
+			/* TODO: No regression test for this branch. */
+			/*
+			 * Execute function will null rhs just once.
+			 *
+			 * The hash lookup path will have scribbled on the lhs argument so
+			 * we need to set it up also (even though we entered this function
+			 * with it already set).
+			 */
+			fcinfo->args[0].value = scalar;
+			fcinfo->args[0].isnull = scalar_isnull;
 			fcinfo->args[1].value = (Datum) 0;
 			fcinfo->args[1].isnull = true;
 
-			res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
-			result = BoolGetDatum(res == 0);
+			result = op->d.scalararraybinsearchop.fn_addr(fcinfo);
 			resultnull = fcinfo->isnull;
 		}
 	}
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 2e93b1f990..847d344e8d 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -21,6 +21,30 @@
 struct ExprEvalStep;
 struct SubscriptingRefState;
 
+typedef struct ScalarArrayOpExprHashEntryData *ScalarArrayOpExprHashEntry;
+typedef struct ScalarArrayOpExprHashTableData *ScalarArrayOpExprHashTable;
+
+typedef struct ScalarArrayOpExprHashEntryData
+{
+	Datum key;
+	uint32		status;			/* hash status */
+	uint32		hash;			/* hash value (cached) */
+} ScalarArrayOpExprHashEntryData;
+
+/* define parameters necessary to generate the tuple hash table interface */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
+#define SH_KEY_TYPE Datum
+#define SH_SCOPE extern
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+typedef struct ScalarArrayOpExprHashTableData
+{
+	saophash_hash *hashtab;	/* underlying hash table */
+	struct ExprEvalStep *op;
+}			ScalarArrayOpExprHashTableData;
+
 /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
 /* expression's interpreter has been initialized */
 #define EEO_FLAG_INTERPRETER_INITIALIZED	(1 << 1)
@@ -555,7 +579,7 @@ typedef struct ExprEvalStep
 		struct
 		{
 			bool		has_nulls;
-			HTAB	   *elements_tab;
+			ScalarArrayOpExprHashTable	   elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
 			/* faster to access without additional indirection: */
-- 
2.17.1

v3-0002-Try-using-dynahash.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Try-using-dynahash.patchDownload
From cd760f5d333a2f1c7b871f05d4a231c0381c660d Mon Sep 17 00:00:00 2001
From: James Coleman <jtc331@gmail.com>
Date: Mon, 27 Apr 2020 08:51:28 -0400
Subject: [PATCH v3 2/3] Try using dynahash

---
 src/backend/executor/execExpr.c           |  29 ++--
 src/backend/executor/execExprInterp.c     | 197 ++++++++++++----------
 src/include/executor/execExpr.h           |   7 +-
 src/test/regress/expected/expressions.out |   7 +
 src/test/regress/sql/expressions.sql      |   2 +
 5 files changed, 138 insertions(+), 104 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c202cc7e89..6249db5426 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -31,6 +31,7 @@
 #include "postgres.h"
 
 #include "access/nbtree.h"
+#include "access/hash.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
 #include "executor/execExpr.h"
@@ -949,10 +950,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
 				Oid			func;
+				Oid			hash_func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
+				FmgrInfo   *hash_finfo;
+				FunctionCallInfo hash_fcinfo;
 				AclResult	aclresult;
 				bool		useBinarySearch = false;
 
@@ -983,11 +987,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				{
 					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
 					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-					Oid			orderingOp;
-					Oid			orderingFunc;
-					Oid			opfamily;
-					Oid			opcintype;
-					int16		strategy;
 					int			nitems;
 
 					/*
@@ -998,21 +997,24 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
 					{
 						/*
-						 * Find the ordering op that matches the originally
+						 * Find the hash op that matches the originally
 						 * planned equality op.
 						 */
-						orderingOp = get_ordering_op_for_equality_op(opexpr->opno, NULL);
-						get_ordering_op_properties(orderingOp, &opfamily, &opcintype, &strategy);
-						orderingFunc = get_opfamily_proc(opfamily, opcintype, opcintype, BTORDER_PROC);
+						useBinarySearch = get_op_hash_functions(opexpr->opno, NULL, &hash_func);
 
 						/*
 						 * But we may not have one, so fall back to the
 						 * default implementation if necessary.
 						 */
-						if (OidIsValid(orderingFunc))
+						if (useBinarySearch)
 						{
-							useBinarySearch = true;
-							func = orderingFunc;
+							hash_finfo = palloc0(sizeof(FmgrInfo));
+							hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
+							fmgr_info(hash_func, hash_finfo);
+							fmgr_info_set_expr((Node *) node, hash_finfo);
+							InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
+													 opexpr->inputcollid, NULL, NULL);
+							InvokeFunctionExecuteHook(hash_func);
 						}
 					}
 				}
@@ -1042,6 +1044,9 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					scratch.d.scalararraybinsearchop.finfo = finfo;
 					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
 					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
+					scratch.d.scalararraybinsearchop.hash_finfo = hash_finfo;
+					scratch.d.scalararraybinsearchop.hash_fcinfo_data = hash_fcinfo;
+					scratch.d.scalararraybinsearchop.hash_fn_addr = hash_finfo->fn_addr;
 				}
 				else
 				{
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5bebafbf0c..e54e807c6b 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -178,8 +178,6 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
-static int	compare_array_elements(const void *a, const void *b, void *arg);
-
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -3593,6 +3591,51 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+static ExprEvalStep *current_saop_op;
+
+/*
+ * Hash function for elements.
+ *
+ * We use the element type's default hash opclass, and the column collation
+ * if the type is collation-sensitive.
+ */
+/* XXX: Name function to be specific to saop binsearch? */
+static uint32
+element_hash(const void *key, Size keysize)
+{
+	Datum hash;
+	FunctionCallInfo fcinfo = current_saop_op->d.scalararraybinsearchop.hash_fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) key);
+	fcinfo->args[0].isnull = false;
+
+	/* The keysize parameter is superfluous here */
+	hash = current_saop_op->d.scalararraybinsearchop.hash_fn_addr(fcinfo);
+
+	return DatumGetUInt32(hash);
+}
+
+/*
+ * Matching function for elements, to be used in hashtable lookups.
+ */
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+element_match(const void *key1, const void *key2, Size keysize)
+{
+	Datum result;
+	FunctionCallInfo fcinfo = current_saop_op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) key1);
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = *((const Datum *) key2);
+	fcinfo->args[1].isnull = false;
+
+	/* The keysize parameter is superfluous here */
+	result = current_saop_op->d.scalararraybinsearchop.fn_addr(fcinfo);
+
+	return DatumGetBool(result) ? 0 : 1;
+}
+
 /*
  * Evaluate "scalar op ANY (const array)".
  *
@@ -3613,16 +3656,19 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
 	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
 	ArrayType  *arr;
+	Datum		scalar = fcinfo->args[0].value;
 	Datum		result;
 	bool		resultnull;
-	bool	   *elem_nulls;
-	int			l = 0,
-				r,
-				res;
+	bool		hashfound;
+	int			res;
+
+	/* If we're only executing once, do we need a way to fall back to the regular loop? */
 
 	/* We don't setup a binary search op if the array const is null. */
 	Assert(!*op->resnull);
 
+	current_saop_op = op;
+
 	/*
 	 * If the scalar is NULL, and the function is strict, return NULL; no
 	 * point in executing the search.
@@ -3634,104 +3680,88 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	}
 
 	/* Preprocess the array the first time we execute the op. */
-	if (op->d.scalararraybinsearchop.elem_values == NULL)
+	if (op->d.scalararraybinsearchop.elements_tab == NULL)
 	{
-		/* Cache the original lhs so we can scribble on it. */
-		Datum		scalar = fcinfo->args[0].value;
-		bool		scalar_isnull = fcinfo->args[0].isnull;
-		int			num_nonnulls = 0;
-		MemoryContext old_cxt;
-		MemoryContext array_cxt;
 		int16		typlen;
 		bool		typbyval;
 		char		typalign;
+		HASHCTL		elem_hash_ctl;
+		int			nitems;
+		int			num_nulls = 0;
+		char	   *s;
+		bits8	   *bitmap;
+		int			bitmask;
 
 		arr = DatumGetArrayTypeP(*op->resvalue);
+		nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 
 		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
 							 &typlen,
 							 &typbyval,
 							 &typalign);
 
-		array_cxt = AllocSetContextCreate(
-										  econtext->ecxt_per_query_memory,
-										  "scalararraybinsearchop context",
-										  ALLOCSET_SMALL_SIZES);
-		old_cxt = MemoryContextSwitchTo(array_cxt);
+		MemSet(&elem_hash_ctl, 0, sizeof(elem_hash_ctl));
+		elem_hash_ctl.keysize = sizeof(Datum);
+		elem_hash_ctl.entrysize = sizeof(Datum);
+		elem_hash_ctl.hash = element_hash;
+		elem_hash_ctl.match = element_match;
+		elem_hash_ctl.hcxt = econtext->ecxt_per_query_memory;
+		op->d.scalararraybinsearchop.elements_tab = hash_create("Scalar array op expr elements",
+								   nitems,
+								   &elem_hash_ctl,
+								   HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+
+		s = (char *) ARR_DATA_PTR(arr);
+		bitmap = ARR_NULLBITMAP(arr);
+		bitmask = 1;
+		for (int i = 0; i < nitems; i++)
+		{
+			Datum		element;
+
+			/* Get array element, checking for NULL. */
+			if (bitmap && (*bitmap & bitmask) == 0)
+			{
+				num_nulls++;
+			}
+			else
+			{
+				element = fetch_att(s, typbyval, typlen);
+				s = att_addlength_pointer(s, typlen, s);
+				s = (char *) att_align_nominal(s, typalign);
 
-		deconstruct_array(arr,
-						  ARR_ELEMTYPE(arr),
-						  typlen, typbyval, typalign,
-						  &op->d.scalararraybinsearchop.elem_values, &elem_nulls, &op->d.scalararraybinsearchop.num_elems);
+				hash_search(op->d.scalararraybinsearchop.elements_tab, (const void *) &element, HASH_ENTER, NULL);
+			}
 
-		/* Remove null entries from the array. */
-		for (int j = 0; j < op->d.scalararraybinsearchop.num_elems; j++)
-		{
-			if (!elem_nulls[j])
-				op->d.scalararraybinsearchop.elem_values[num_nonnulls++] = op->d.scalararraybinsearchop.elem_values[j];
+			/* Advance bitmap pointer if any. */
+			if (bitmap)
+			{
+				bitmask <<= 1;
+				if (bitmask == 0x100)
+				{
+					bitmap++;
+					bitmask = 1;
+				}
+			}
 		}
 
 		/*
 		 * Remember if we had any nulls so that we know if we need to execute
 		 * non-strict functions with a null lhs value if no match is found.
 		 */
-		op->d.scalararraybinsearchop.has_nulls = num_nonnulls < op->d.scalararraybinsearchop.num_elems;
-		op->d.scalararraybinsearchop.num_elems = num_nonnulls;
+		op->d.scalararraybinsearchop.has_nulls = num_nulls > 0;
 
 		/*
-		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
-		 * rhs values are guaranteed to be non-null.
+		 * We only setup a binary search op if we have > 8 elements, so we don't
+		 * need to worry about adding an optimization for the empty array case.
 		 */
-		fcinfo->args[0].isnull = false;
-		fcinfo->args[1].isnull = false;
-
-		/* Sort the array and remove duplicate elements. */
-		qsort_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
-				  compare_array_elements, op);
-		op->d.scalararraybinsearchop.num_elems = qunique_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
-															 compare_array_elements, op);
-
-		/* Restore the lhs value after we scribbed on it for sorting. */
-		fcinfo->args[0].value = scalar;
-		fcinfo->args[0].isnull = scalar_isnull;
-
-		MemoryContextSwitchTo(old_cxt);
+		Assert(nitems > 0);
 	}
 
-	/*
-	 * We only setup a binary search op if we have > 8 elements, so we don't
-	 * need to worry about adding an optimization for the empty array case.
-	 */
-	Assert(!(op->d.scalararraybinsearchop.num_elems <= 0 && !op->d.scalararraybinsearchop.has_nulls));
-
-	/* Assume no match will be found until proven otherwise. */
-	result = BoolGetDatum(false);
+	/* Check the hash to see if we have a match. */
+	hash_search(op->d.scalararraybinsearchop.elements_tab, (const void *) &scalar, HASH_FIND, &hashfound);
+	result = BoolGetDatum(hashfound);
 	resultnull = false;
 
-	/* Binary search through the array. */
-	r = op->d.scalararraybinsearchop.num_elems - 1;
-	while (l <= r)
-	{
-		int			i = (l + r) / 2;
-
-		fcinfo->args[1].value = op->d.scalararraybinsearchop.elem_values[i];
-
-		/* Call comparison function */
-		fcinfo->isnull = false;
-		res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
-
-		if (res == 0)
-		{
-			result = BoolGetDatum(true);
-			resultnull = false;
-			break;
-		}
-		else if (res > 0)
-			l = i + 1;
-		else
-			r = i - 1;
-	}
-
 	/*
 	 * If we didn't find a match in the array, we still might need to handle
 	 * the possibility of null values (we've previously removed them from the
@@ -3761,19 +3791,6 @@ ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *
 	*op->resnull = resultnull;
 }
 
-/* XXX: Name function to be specific to saop binsearch? */
-static int
-compare_array_elements(const void *a, const void *b, void *arg)
-{
-	ExprEvalStep *op = (ExprEvalStep *) arg;
-	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
-
-	fcinfo->args[0].value = *((const Datum *) a);
-	fcinfo->args[1].value = *((const Datum *) b);
-
-	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
-}
-
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index ac4478d060..2e93b1f990 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -554,13 +554,16 @@ typedef struct ExprEvalStep
 		/* for EEOP_SCALARARRAYOP_BINSEARCH */
 		struct
 		{
-			int			num_elems;
 			bool		has_nulls;
-			Datum	   *elem_values;
+			HTAB	   *elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
 			/* faster to access without additional indirection: */
 			PGFunction	fn_addr;	/* actual call address */
+			FmgrInfo   *hash_finfo;	/* function's lookup data */
+			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	hash_fn_addr;	/* actual call address */
 		}			scalararraybinsearchop;
 
 		/* for EEOP_XMLEXPR */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 55b57b9c59..f37dfe1ce2 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -197,3 +197,10 @@ select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
  
 (1 row)
 
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ t
+(1 row)
+
+-- TODO: test non-strict op?
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 3cb850d838..c30fe66c5e 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -76,3 +76,5 @@ select 1 in (null, null, null, null, null, null, null, null, null, null, null);
 select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- TODO: test non-strict op?
-- 
2.17.1

v3-0001-Binary-search-const-arrays-in-OR-d-ScalarArrayOps.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Binary-search-const-arrays-in-OR-d-ScalarArrayOps.patchDownload
From 08742543d7865d5f25c24c26bf1014924035c9eb Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Fri, 10 Apr 2020 21:40:50 +0000
Subject: [PATCH v3 1/3] Binary search const arrays in OR'd ScalarArrayOps

Currently all scalar array op expressions execute as a linear search
through the array argument. However when OR semantics are desired it's
possible to instead use a binary search. Here we apply that optimization
to constant arrays (so we don't need to worry about teaching expression
execution when params change) of at least length 9 (since very short
arrays average to the same number of comparisons for linear searches and
thus avoid the preprocessing necessary for a binary search).
---
 src/backend/executor/execExpr.c           |  79 +++++++--
 src/backend/executor/execExprInterp.c     | 193 ++++++++++++++++++++++
 src/include/executor/execExpr.h           |  14 ++
 src/test/regress/expected/expressions.out |  39 +++++
 src/test/regress/sql/expressions.sql      |  11 ++
 5 files changed, 326 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c6a77bd66f..c202cc7e89 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -49,6 +49,7 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
+#define MIN_ARRAY_SIZE_FOR_BINARY_SEARCH 9
 
 typedef struct LastAttnumInfo
 {
@@ -947,11 +948,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				bool		useBinarySearch = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -964,12 +967,58 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				func = opexpr->opfuncid;
+
+				/*
+				 * If we have a constant array and want OR semantics, then we
+				 * can use a binary search to implement the op instead of
+				 * looping through the entire array for each execution.
+				 */
+				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
+					!((Const *) arrayarg)->constisnull)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					Oid			orderingOp;
+					Oid			orderingFunc;
+					Oid			opfamily;
+					Oid			opcintype;
+					int16		strategy;
+					int			nitems;
+
+					/*
+					 * Only do the optimization if we have a large enough
+					 * array to make it worth it.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					if (nitems >= MIN_ARRAY_SIZE_FOR_BINARY_SEARCH)
+					{
+						/*
+						 * Find the ordering op that matches the originally
+						 * planned equality op.
+						 */
+						orderingOp = get_ordering_op_for_equality_op(opexpr->opno, NULL);
+						get_ordering_op_properties(orderingOp, &opfamily, &opcintype, &strategy);
+						orderingFunc = get_opfamily_proc(opfamily, opcintype, opcintype, BTORDER_PROC);
+
+						/*
+						 * But we may not have one, so fall back to the
+						 * default implementation if necessary.
+						 */
+						if (OidIsValid(orderingFunc))
+						{
+							useBinarySearch = true;
+							func = orderingFunc;
+						}
+					}
+				}
+
+				InvokeFunctionExecuteHook(func);
+				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -981,18 +1030,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * be overwritten by EEOP_SCALARARRAYOP[_BINSEARCH], and will
+				 * not be passed to any other expression.
 				 */
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				if (useBinarySearch)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_BINSEARCH;
+					scratch.d.scalararraybinsearchop.finfo = finfo;
+					scratch.d.scalararraybinsearchop.fcinfo_data = fcinfo;
+					scratch.d.scalararraybinsearchop.fn_addr = finfo->fn_addr;
+				}
+				else
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				}
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 113ed1547c..5bebafbf0c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,6 +76,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -177,6 +178,8 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 					   AggStatePerGroup pergroup,
 					   ExprContext *aggcontext, int setno);
 
+static int	compare_array_elements(const void *a, const void *b, void *arg);
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -425,6 +428,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_SCALARARRAYOP_BINSEARCH,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1464,6 +1468,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_BINSEARCH)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpBinSearch(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3581,6 +3593,187 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it binary searches through the array for a
+ * match after sorting the array and removing null and duplicate entries.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR.  Of course we short-circuit as soon as the result
+ * is known.
+ */
+void
+ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+	bool		strictfunc = op->d.scalararraybinsearchop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		result;
+	bool		resultnull;
+	bool	   *elem_nulls;
+	int			l = 0,
+				r,
+				res;
+
+	/* We don't setup a binary search op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (op->d.scalararraybinsearchop.elem_values == NULL)
+	{
+		/* Cache the original lhs so we can scribble on it. */
+		Datum		scalar = fcinfo->args[0].value;
+		bool		scalar_isnull = fcinfo->args[0].isnull;
+		int			num_nonnulls = 0;
+		MemoryContext old_cxt;
+		MemoryContext array_cxt;
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		array_cxt = AllocSetContextCreate(
+										  econtext->ecxt_per_query_memory,
+										  "scalararraybinsearchop context",
+										  ALLOCSET_SMALL_SIZES);
+		old_cxt = MemoryContextSwitchTo(array_cxt);
+
+		deconstruct_array(arr,
+						  ARR_ELEMTYPE(arr),
+						  typlen, typbyval, typalign,
+						  &op->d.scalararraybinsearchop.elem_values, &elem_nulls, &op->d.scalararraybinsearchop.num_elems);
+
+		/* Remove null entries from the array. */
+		for (int j = 0; j < op->d.scalararraybinsearchop.num_elems; j++)
+		{
+			if (!elem_nulls[j])
+				op->d.scalararraybinsearchop.elem_values[num_nonnulls++] = op->d.scalararraybinsearchop.elem_values[j];
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararraybinsearchop.has_nulls = num_nonnulls < op->d.scalararraybinsearchop.num_elems;
+		op->d.scalararraybinsearchop.num_elems = num_nonnulls;
+
+		/*
+		 * Setup the fcinfo for sorting. We've removed nulls, so both lhs and
+		 * rhs values are guaranteed to be non-null.
+		 */
+		fcinfo->args[0].isnull = false;
+		fcinfo->args[1].isnull = false;
+
+		/* Sort the array and remove duplicate elements. */
+		qsort_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+				  compare_array_elements, op);
+		op->d.scalararraybinsearchop.num_elems = qunique_arg(op->d.scalararraybinsearchop.elem_values, op->d.scalararraybinsearchop.num_elems, sizeof(Datum),
+															 compare_array_elements, op);
+
+		/* Restore the lhs value after we scribbed on it for sorting. */
+		fcinfo->args[0].value = scalar;
+		fcinfo->args[0].isnull = scalar_isnull;
+
+		MemoryContextSwitchTo(old_cxt);
+	}
+
+	/*
+	 * We only setup a binary search op if we have > 8 elements, so we don't
+	 * need to worry about adding an optimization for the empty array case.
+	 */
+	Assert(!(op->d.scalararraybinsearchop.num_elems <= 0 && !op->d.scalararraybinsearchop.has_nulls));
+
+	/* Assume no match will be found until proven otherwise. */
+	result = BoolGetDatum(false);
+	resultnull = false;
+
+	/* Binary search through the array. */
+	r = op->d.scalararraybinsearchop.num_elems - 1;
+	while (l <= r)
+	{
+		int			i = (l + r) / 2;
+
+		fcinfo->args[1].value = op->d.scalararraybinsearchop.elem_values[i];
+
+		/* Call comparison function */
+		fcinfo->isnull = false;
+		res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+
+		if (res == 0)
+		{
+			result = BoolGetDatum(true);
+			resultnull = false;
+			break;
+		}
+		else if (res > 0)
+			l = i + 1;
+		else
+			r = i - 1;
+	}
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararraybinsearchop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/* Had nulls, so strict function implies null. */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* Execute function will null rhs just once. */
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			res = DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+			result = BoolGetDatum(res == 0);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
+/* XXX: Name function to be specific to saop binsearch? */
+static int
+compare_array_elements(const void *a, const void *b, void *arg)
+{
+	ExprEvalStep *op = (ExprEvalStep *) arg;
+	FunctionCallInfo fcinfo = op->d.scalararraybinsearchop.fcinfo_data;
+
+	fcinfo->args[0].value = *((const Datum *) a);
+	fcinfo->args[1].value = *((const Datum *) b);
+
+	return DatumGetInt32(op->d.scalararraybinsearchop.fn_addr(fcinfo));
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index dbe8649a57..ac4478d060 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -213,6 +213,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_SCALARARRAYOP_BINSEARCH,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -550,6 +551,18 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_SCALARARRAYOP_BINSEARCH */
+		struct
+		{
+			int			num_elems;
+			bool		has_nulls;
+			Datum	   *elem_values;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+		}			scalararraybinsearchop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -728,6 +741,7 @@ extern void ExecEvalSubscriptingRefAssign(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalScalarArrayOpBinSearch(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 4f4deaec22..55b57b9c59 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,42 @@ select count(*) from date_tbl
     12
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..3cb850d838 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,14 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr binary search optimization
+--
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
-- 
2.17.1

#40Heikki Linnakangas
hlinnaka@iki.fi
In reply to: James Coleman (#39)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 01/05/2020 05:20, James Coleman wrote:

On Tue, Apr 28, 2020 at 8:25 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
...

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

I've attached a patch series that includes switching to simplehash.
Obviously we'd really just collapse all of these patches, but it's
perhaps interesting to see the changes required to use each style
(binary search, dynahash, simplehash).

As before, there are clearly comments and naming things to be
addressed, but the implementation should be reasonably clean.

Looks good, aside from the cleanup work that you mentioned. There are a
few more cases that I think you could easily handle with very little
extra code:

You could also apply the optimization for non-Const expressions, as long
as they're stable (i.e. !contain_volatile_functions(expr)).

Deconstructing the array Datum into a simple C array on first call would
be a win even for very small arrays and for AND semantics, even if you
don't use a hash table.

- Heikki

#41James Coleman
jtc331@gmail.com
In reply to: Heikki Linnakangas (#40)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Wed, Aug 19, 2020 at 3:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 01/05/2020 05:20, James Coleman wrote:

On Tue, Apr 28, 2020 at 8:25 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
...

Any particular reasons to pick dynahash over simplehash? ISTM we're
using simplehash elsewhere in the executor (grouping, tidbitmap, ...),
while there are not many places using dynahash for simple short-lived
hash tables. Of course, that alone is a weak reason to insist on using
simplehash here, but I suppose there were reasons for not using dynahash
and we'll end up facing the same issues here.

I've attached a patch series that includes switching to simplehash.
Obviously we'd really just collapse all of these patches, but it's
perhaps interesting to see the changes required to use each style
(binary search, dynahash, simplehash).

As before, there are clearly comments and naming things to be
addressed, but the implementation should be reasonably clean.

Looks good, aside from the cleanup work that you mentioned. There are a
few more cases that I think you could easily handle with very little
extra code:

You could also apply the optimization for non-Const expressions, as long
as they're stable (i.e. !contain_volatile_functions(expr)).

Is that true? Don't we also have to worry about whether or not the
value is stable (i.e., know when a param has changed)? There have been
discussions about being able to cache stable subexpressions, and my
understanding was that we'd need to have that infrastructure (along
with the necessarily invalidation when the param changes) to be able
to use this for non-Const expressions.

Deconstructing the array Datum into a simple C array on first call would
be a win even for very small arrays and for AND semantics, even if you
don't use a hash table.

Because you wouldn't have to repeatedly detoast it? Or some other
reason I'm not thinking of? My intuition would have been that (aside
from detoasting if necessary) there wouldn't be much real overhead in,
for example, an array storing integers.

James

#42Heikki Linnakangas
hlinnaka@iki.fi
In reply to: James Coleman (#41)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 08/09/2020 22:25, James Coleman wrote:

On Wed, Aug 19, 2020 at 3:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

You could also apply the optimization for non-Const expressions, as long
as they're stable (i.e. !contain_volatile_functions(expr)).

Is that true? Don't we also have to worry about whether or not the
value is stable (i.e., know when a param has changed)? There have been
discussions about being able to cache stable subexpressions, and my
understanding was that we'd need to have that infrastructure (along
with the necessarily invalidation when the param changes) to be able
to use this for non-Const expressions.

Yeah, you're right, you'd have to also check for PARAM_EXEC Params. And
Vars. I think the conditions are the same as those checked in
match_clause_to_partition_key() in partprune.c (it's a long function,
search for "if (!IsA(rightop, Const))"). Not sure it's worth the
trouble, then. But it would be nice to handle queries like "WHERE column
= ANY ($1)"

Deconstructing the array Datum into a simple C array on first call would
be a win even for very small arrays and for AND semantics, even if you
don't use a hash table.

Because you wouldn't have to repeatedly detoast it? Or some other
reason I'm not thinking of? My intuition would have been that (aside
from detoasting if necessary) there wouldn't be much real overhead in,
for example, an array storing integers.

Dealing with NULLs and different element sizes in the array is pretty
complicated. Looping through the array currently looks like this:

/* Loop over the array elements */
s = (char *) ARR_DATA_PTR(arr);
bitmap = ARR_NULLBITMAP(arr);
bitmask = 1;

for (int i = 0; i < nitems; i++)
{
Datum elt;
Datum thisresult;

/* Get array element, checking for NULL */
if (bitmap && (*bitmap & bitmask) == 0)
{
fcinfo->args[1].value = (Datum) 0;
fcinfo->args[1].isnull = true;
}
else
{
elt = fetch_att(s, typbyval, typlen);
s = att_addlength_pointer(s, typlen, s);
s = (char *) att_align_nominal(s, typalign);
fcinfo->args[1].value = elt;
fcinfo->args[1].isnull = false;
}

[do stuff with Datum/isnull]

/* advance bitmap pointer if any */
if (bitmap)
{
bitmask <<= 1;
if (bitmask == 0x100)
{
bitmap++;
bitmask = 1;
}
}
}

Compared with just:

for (int i = 0; i < nitems; i++)
{
Datum elt = datums[i];

[do stuff with the Datum]
}

I'm not sure how much difference that makes, but I presume it's not
zero, and it seems like an easy win when you have the code to deal with
the Datum array representation anyway.

- Heikki

#43James Coleman
jtc331@gmail.com
In reply to: Heikki Linnakangas (#42)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, Sep 8, 2020 at 4:37 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 08/09/2020 22:25, James Coleman wrote:

On Wed, Aug 19, 2020 at 3:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

You could also apply the optimization for non-Const expressions, as long
as they're stable (i.e. !contain_volatile_functions(expr)).

Is that true? Don't we also have to worry about whether or not the
value is stable (i.e., know when a param has changed)? There have been
discussions about being able to cache stable subexpressions, and my
understanding was that we'd need to have that infrastructure (along
with the necessarily invalidation when the param changes) to be able
to use this for non-Const expressions.

Yeah, you're right, you'd have to also check for PARAM_EXEC Params. And
Vars. I think the conditions are the same as those checked in
match_clause_to_partition_key() in partprune.c (it's a long function,
search for "if (!IsA(rightop, Const))"). Not sure it's worth the
trouble, then. But it would be nice to handle queries like "WHERE column
= ANY ($1)"

If I'm understanding properly you're imagining something in the form of:

with x as (select '{1,2,3,4,5,6,7,8,9,10}'::int[])
select * from t where i = any ((select * from x)::int[]);

I've been playing around with this and currently have these checks:

contain_var_clause((Node *) arrayarg)
contain_volatile_functions((Node *) arrayarg)
contain_exec_param((Node *) arrayarg, NIL)

(the last one I had to modify the function to handle empty lists)

If any are true, then have to disable the optimization. But for
queries in the form above the test contain_exec_param((Node *)
arrayarg, NIL) evaluates to true, even though we know from looking at
the query that the array subexpression is stable for the length of the
query.

Am I misunderstanding what you're going for? Or is there a way to
confirm the expr, although an exec param, won't change?

Another interesting thing that this would imply is that we'd either have to:

1. Remove the array length check altogether,
2. Always use the hash when have a non-Const, but when a Const only if
the array length check passes, or
3. Make this new expr op more fully featured by teaching it how to use
either a straight loop through a deconstructed array or use the hash.

That last option feeds into further discussion in the below:

Deconstructing the array Datum into a simple C array on first call would
be a win even for very small arrays and for AND semantics, even if you
don't use a hash table.

Because you wouldn't have to repeatedly detoast it? Or some other
reason I'm not thinking of? My intuition would have been that (aside
from detoasting if necessary) there wouldn't be much real overhead in,
for example, an array storing integers.

Dealing with NULLs and different element sizes in the array is pretty
complicated. Looping through the array currently looks like this:

/* Loop over the array elements */
s = (char *) ARR_DATA_PTR(arr);
bitmap = ARR_NULLBITMAP(arr);
bitmask = 1;

for (int i = 0; i < nitems; i++)
{
Datum elt;
Datum thisresult;

/* Get array element, checking for NULL */
if (bitmap && (*bitmap & bitmask) == 0)
{
fcinfo->args[1].value = (Datum) 0;
fcinfo->args[1].isnull = true;
}
else
{
elt = fetch_att(s, typbyval, typlen);
s = att_addlength_pointer(s, typlen, s);
s = (char *) att_align_nominal(s, typalign);
fcinfo->args[1].value = elt;
fcinfo->args[1].isnull = false;
}

[do stuff with Datum/isnull]

/* advance bitmap pointer if any */
if (bitmap)
{
bitmask <<= 1;
if (bitmask == 0x100)
{
bitmap++;
bitmask = 1;
}
}
}

Compared with just:

for (int i = 0; i < nitems; i++)
{
Datum elt = datums[i];

[do stuff with the Datum]
}

I'm not sure how much difference that makes, but I presume it's not
zero, and it seems like an easy win when you have the code to deal with
the Datum array representation anyway.

Doing this would necessitate option 3 above: we'd have to have this
new expr op be able both to use a hash or alternatively do a normal
loop.

Being able to use this in more cases than just a Const array expr is
certainly interesting, but I'm not sure yet about the feasibility or
desirability of that at this point given the above restrictions.

One other point in favor of the additional complexity here is that
it's likely that the above described runtime switching between hash
and loop would be necessary (for this optimization to come into play)
if caching of stable subexpressions ever lands. I have some interest
in working on that...but it's also a large project.

James

#44James Coleman
jtc331@gmail.com
In reply to: James Coleman (#43)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, Sep 11, 2020 at 5:11 PM James Coleman <jtc331@gmail.com> wrote:

On Tue, Sep 8, 2020 at 4:37 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 08/09/2020 22:25, James Coleman wrote:

On Wed, Aug 19, 2020 at 3:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

You could also apply the optimization for non-Const expressions, as long
as they're stable (i.e. !contain_volatile_functions(expr)).

Is that true? Don't we also have to worry about whether or not the
value is stable (i.e., know when a param has changed)? There have been
discussions about being able to cache stable subexpressions, and my
understanding was that we'd need to have that infrastructure (along
with the necessarily invalidation when the param changes) to be able
to use this for non-Const expressions.

Yeah, you're right, you'd have to also check for PARAM_EXEC Params. And
Vars. I think the conditions are the same as those checked in
match_clause_to_partition_key() in partprune.c (it's a long function,
search for "if (!IsA(rightop, Const))"). Not sure it's worth the
trouble, then. But it would be nice to handle queries like "WHERE column
= ANY ($1)"

If I'm understanding properly you're imagining something in the form of:

with x as (select '{1,2,3,4,5,6,7,8,9,10}'::int[])
select * from t where i = any ((select * from x)::int[]);

I've been playing around with this and currently have these checks:

contain_var_clause((Node *) arrayarg)
contain_volatile_functions((Node *) arrayarg)
contain_exec_param((Node *) arrayarg, NIL)

(the last one I had to modify the function to handle empty lists)

If any are true, then have to disable the optimization. But for
queries in the form above the test contain_exec_param((Node *)
arrayarg, NIL) evaluates to true, even though we know from looking at
the query that the array subexpression is stable for the length of the
query.

Am I misunderstanding what you're going for? Or is there a way to
confirm the expr, although an exec param, won't change?

Another interesting thing that this would imply is that we'd either have to:

1. Remove the array length check altogether,
2. Always use the hash when have a non-Const, but when a Const only if
the array length check passes, or
3. Make this new expr op more fully featured by teaching it how to use
either a straight loop through a deconstructed array or use the hash.

That last option feeds into further discussion in the below:

Deconstructing the array Datum into a simple C array on first call would
be a win even for very small arrays and for AND semantics, even if you
don't use a hash table.

Because you wouldn't have to repeatedly detoast it? Or some other
reason I'm not thinking of? My intuition would have been that (aside
from detoasting if necessary) there wouldn't be much real overhead in,
for example, an array storing integers.

Dealing with NULLs and different element sizes in the array is pretty
complicated. Looping through the array currently looks like this:

/* Loop over the array elements */
s = (char *) ARR_DATA_PTR(arr);
bitmap = ARR_NULLBITMAP(arr);
bitmask = 1;

for (int i = 0; i < nitems; i++)
{
Datum elt;
Datum thisresult;

/* Get array element, checking for NULL */
if (bitmap && (*bitmap & bitmask) == 0)
{
fcinfo->args[1].value = (Datum) 0;
fcinfo->args[1].isnull = true;
}
else
{
elt = fetch_att(s, typbyval, typlen);
s = att_addlength_pointer(s, typlen, s);
s = (char *) att_align_nominal(s, typalign);
fcinfo->args[1].value = elt;
fcinfo->args[1].isnull = false;
}

[do stuff with Datum/isnull]

/* advance bitmap pointer if any */
if (bitmap)
{
bitmask <<= 1;
if (bitmask == 0x100)
{
bitmap++;
bitmask = 1;
}
}
}

Compared with just:

for (int i = 0; i < nitems; i++)
{
Datum elt = datums[i];

[do stuff with the Datum]
}

I'm not sure how much difference that makes, but I presume it's not
zero, and it seems like an easy win when you have the code to deal with
the Datum array representation anyway.

Doing this would necessitate option 3 above: we'd have to have this
new expr op be able both to use a hash or alternatively do a normal
loop.

Being able to use this in more cases than just a Const array expr is
certainly interesting, but I'm not sure yet about the feasibility or
desirability of that at this point given the above restrictions.

One other point in favor of the additional complexity here is that
it's likely that the above described runtime switching between hash
and loop would be necessary (for this optimization to come into play)
if caching of stable subexpressions ever lands. I have some interest
in working on that...but it's also a large project.

I've attached a cleaned up patch. Last CF it was listed in is
https://commitfest.postgresql.org/29/2542/ -- what's the appropriate
step to take here given it's an already existing patch, but not yet
moved into recent CFs?

Thanks,
James

Attachments:

v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patchapplication/octet-stream; name=v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patchDownload
From c68fe7488be6d93f29a2f2bdef91da7c8fd357ef Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Fri, 10 Apr 2020 21:40:50 +0000
Subject: [PATCH v4] Hash lookup const arrays in OR'd ScalarArrayOps

Currently all scalar array op expressions execute as a linear search
through the array argument. However when OR semantics are desired it's
possible to instead use a hash lookup. Here we apply that optimization
to constant arrays (so we don't need to worry about teaching expression
execution when params change) of at least length 9 (since very short
arrays average to the same number of comparisons for linear searches and
thus avoid the preprocessing necessary to build the hash).
---
 src/backend/executor/execExpr.c           |  84 +++++++-
 src/backend/executor/execExprInterp.c     | 234 ++++++++++++++++++++++
 src/include/executor/execExpr.h           |  41 ++++
 src/test/regress/expected/expressions.out |  45 +++++
 src/test/regress/sql/expressions.sql      |  12 ++
 5 files changed, 406 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2e463f5499..4b9714f071 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -31,6 +31,7 @@
 #include "postgres.h"
 
 #include "access/nbtree.h"
+#include "access/hash.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
 #include "executor/execExpr.h"
@@ -50,6 +51,7 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
+#define MIN_ARRAY_SIZE_FOR_SAOP_HASH 9
 
 typedef struct LastAttnumInfo
 {
@@ -944,11 +946,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				bool		useHash = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -961,12 +965,62 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				func = opexpr->opfuncid;
+
+				/*
+				 * If we have a constant array and want OR semantics, then we
+				 * implement the op with a hash lookup instead of looping
+				 * through the entire array for each execution.
+				 */
+				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
+					!((Const *) arrayarg)->constisnull)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
+
+					/*
+					 * Only do the optimization if we have a large enough
+					 * array to make it worth it.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					if (nitems >= MIN_ARRAY_SIZE_FOR_SAOP_HASH)
+					{
+						Oid			hash_func;
+
+						/*
+						 * Find the hash op that matches the originally planned
+						 * equality op. If we don't have one, we'll just fall
+						 * back to the default linear scan implementation.
+						 */
+						useHash = get_op_hash_functions(opexpr->opno, NULL, &hash_func);
+
+						if (useHash)
+						{
+							FmgrInfo   *hash_finfo;
+							FunctionCallInfo hash_fcinfo;
+
+							hash_finfo = palloc0(sizeof(FmgrInfo));
+							hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
+							fmgr_info(hash_func, hash_finfo);
+							fmgr_info_set_expr((Node *) node, hash_finfo);
+							InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
+													 opexpr->inputcollid, NULL, NULL);
+							InvokeFunctionExecuteHook(hash_func);
+
+							scratch.d.scalararrayhashedop.hash_finfo = hash_finfo;
+							scratch.d.scalararrayhashedop.hash_fcinfo_data = hash_fcinfo;
+							scratch.d.scalararrayhashedop.hash_fn_addr = hash_finfo->fn_addr;
+						}
+					}
+				}
+
+				InvokeFunctionExecuteHook(func);
+				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -978,18 +1032,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * be overwritten by EEOP_SCALARARRAYOP[_HASHED], and will
+				 * not be passed to any other expression.
 				 */
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				if (useHash)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_HASHED;
+					scratch.d.scalararrayhashedop.finfo = finfo;
+					scratch.d.scalararrayhashedop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayhashedop.fn_addr = finfo->fn_addr;
+				}
+				else
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				}
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 6308286d8c..d7968b299d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,6 +76,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -178,6 +179,27 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate
 															  ExprContext *aggcontext,
 															  int setno);
 
+static bool
+saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2);
+static uint32 saop_element_hash(struct saophash_hash *tb, Datum key);
+
+/*
+ * Define parameters for ScalarArrayOpExpr hash table code generation. The interface is
+ * *also* declared in execnodes.h (to generate the types, which are externally
+ * visible).
+ */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
+#define SH_KEY_TYPE Datum
+#define SH_KEY key
+#define SH_HASH_KEY(tb, key) saop_element_hash(tb, key)
+#define SH_EQUAL(tb, a, b) saop_hash_element_match(tb, a, b)
+#define SH_SCOPE extern
+#define SH_STORE_HASH
+#define SH_GET_HASH(tb, a) a->hash
+#define SH_DEFINE
+#include "lib/simplehash.h"
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -426,6 +448,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_SCALARARRAYOP_HASHED,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1436,6 +1459,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_HASHED)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpHashed(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3345,6 +3376,209 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Hash function for scalar array hash op elements.
+ *
+ * We use the element type's default hash opclass, and the column collation
+ * if the type is collation-sensitive.
+ */
+static uint32
+saop_element_hash(struct saophash_hash *tb, Datum key)
+{
+	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.scalararrayhashedop.hash_fcinfo_data;
+	Datum hash;
+
+	fcinfo->args[0].value = key;
+	fcinfo->args[0].isnull = false;
+
+	hash = elements_tab->op->d.scalararrayhashedop.hash_fn_addr(fcinfo);
+
+	return DatumGetUInt32(hash);
+}
+
+/*
+ * Matching function for scalar array hash op elements, to be used in hashtable
+ * lookups.
+ */
+static bool
+saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2)
+{
+	Datum result;
+
+	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.scalararrayhashedop.fcinfo_data;
+
+	fcinfo->args[0].value = key1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = key2;
+	fcinfo->args[1].isnull = false;
+
+	result = elements_tab->op->d.scalararrayhashedop.fn_addr(fcinfo);
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it performs a hashtable lookup to determine
+ * if the array contains the value.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean.
+ */
+void
+ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ScalarArrayOpExprHashTable elements_tab = op->d.scalararrayhashedop.elements_tab;
+	FunctionCallInfo fcinfo = op->d.scalararrayhashedop.fcinfo_data;
+	bool		strictfunc = op->d.scalararrayhashedop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		scalar = fcinfo->args[0].value;
+	bool		scalar_isnull = fcinfo->args[0].isnull;
+	Datum		result;
+	bool		resultnull;
+	bool		hashfound;
+
+	/* We don't setup a hashed scalar array op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (elements_tab == NULL)
+	{
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+		int			nitems;
+		int			num_nulls = 0;
+		char	   *s;
+		bits8	   *bitmap;
+		int			bitmask;
+		MemoryContext oldcontext;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+		nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+		elements_tab = (ScalarArrayOpExprHashTable) palloc(sizeof(ScalarArrayOpExprHashTableData));
+		op->d.scalararrayhashedop.elements_tab = elements_tab;
+		elements_tab->op = op;
+		elements_tab->hashtab = saophash_create(CurrentMemoryContext, nitems, elements_tab);
+
+		MemoryContextSwitchTo(oldcontext);
+
+		s = (char *) ARR_DATA_PTR(arr);
+		bitmap = ARR_NULLBITMAP(arr);
+		bitmask = 1;
+		for (int i = 0; i < nitems; i++)
+		{
+			Datum		element;
+
+			/* Get array element, checking for NULL. */
+			if (bitmap && (*bitmap & bitmask) == 0)
+			{
+				num_nulls++;
+			}
+			else
+			{
+				element = fetch_att(s, typbyval, typlen);
+				s = att_addlength_pointer(s, typlen, s);
+				s = (char *) att_align_nominal(s, typalign);
+
+				saophash_insert(elements_tab->hashtab, element, &hashfound);
+			}
+
+			/* Advance bitmap pointer if any. */
+			if (bitmap)
+			{
+				bitmask <<= 1;
+				if (bitmask == 0x100)
+				{
+					bitmap++;
+					bitmask = 1;
+				}
+			}
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararrayhashedop.has_nulls = num_nulls > 0;
+
+		/*
+		 * We only setup a binary search op if we have > 8 elements, so we don't
+		 * need to worry about adding an optimization for the empty array case.
+		 */
+		Assert(nitems > 0);
+	}
+
+	/* Check the hash to see if we have a match. */
+	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
+
+	result = BoolGetDatum(hashfound);
+	resultnull = false;
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararrayhashedop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/*
+			 * Had nulls and is a strict function, so instead of executing the
+			 * function onces with a null rhs, we can assume null.
+			 */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* TODO: No regression test for this branch. */
+			/*
+			 * Execute function will null rhs just once.
+			 *
+			 * The hash lookup path will have scribbled on the lhs argument so
+			 * we need to set it up also (even though we entered this function
+			 * with it already set).
+			 */
+			fcinfo->args[0].value = scalar;
+			fcinfo->args[0].isnull = scalar_isnull;
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			result = op->d.scalararrayhashedop.fn_addr(fcinfo);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 1b7f9865b0..dc298e3597 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -21,6 +21,30 @@
 struct ExprEvalStep;
 struct SubscriptingRefState;
 
+typedef struct ScalarArrayOpExprHashEntryData *ScalarArrayOpExprHashEntry;
+typedef struct ScalarArrayOpExprHashTableData *ScalarArrayOpExprHashTable;
+
+typedef struct ScalarArrayOpExprHashEntryData
+{
+	Datum key;
+	uint32		status;			/* hash status */
+	uint32		hash;			/* hash value (cached) */
+} ScalarArrayOpExprHashEntryData;
+
+/* define parameters necessary to generate the tuple hash table interface */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
+#define SH_KEY_TYPE Datum
+#define SH_SCOPE extern
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+typedef struct ScalarArrayOpExprHashTableData
+{
+	saophash_hash *hashtab;	/* underlying hash table */
+	struct ExprEvalStep *op;
+}			ScalarArrayOpExprHashTableData;
+
 /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
 /* expression's interpreter has been initialized */
 #define EEO_FLAG_INTERPRETER_INITIALIZED	(1 << 1)
@@ -218,6 +242,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_SCALARARRAYOP_HASHED,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -554,6 +579,21 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_SCALARARRAYOP_HASHED */
+		struct
+		{
+			bool		has_nulls;
+			ScalarArrayOpExprHashTable	   elements_tab;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+			FmgrInfo   *hash_finfo;	/* function's lookup data */
+			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	hash_fn_addr;	/* actual call address */
+		}			scalararrayhashedop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -725,6 +765,7 @@ extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 05a6eb07b2..42e1c0d1f2 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,48 @@ select count(*) from date_tbl
     13
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr hash optimization
+--
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ t
+(1 row)
+
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..ef2738067b 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,15 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr hash optimization
+--
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
-- 
2.20.1

#45David Rowley
dgrowleyml@gmail.com
In reply to: James Coleman (#44)
2 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 20 Mar 2021 at 09:41, James Coleman <jtc331@gmail.com> wrote:

I've attached a cleaned up patch. Last CF it was listed in is
https://commitfest.postgresql.org/29/2542/ -- what's the appropriate
step to take here given it's an already existing patch, but not yet
moved into recent CFs?

I had a look at this patch. I like the idea of using a simplehash.h
hash table to hash the constant values so that repeat lookups can be
performed much more quickly, however, I'm a bit concerned that there
are quite a few places in the code where we often just execute a
ScalarArrayOpExpr once and I'm a bit worried that we'll slow down
expression evaluation of those cases.

The two cases that I have in mind are:

1. eval_const_expressions() where we use the executor to evaluate the
ScalarArrayOpExpr to see if the result is Const.
2. CHECK constraints with IN clauses and single-row INSERTs.

I tried to benchmark both of these but I'm struggling to get stable
enough performance for #2, even with fsync=off. Sometimes I'm getting
results 2.5x slower than other runs.

For benchmarking #1 I'm also not too sure I'm getting stable enough
results for them to mean anything.

I was running:

create table a (a int);

bench.sql: explain select * from a where a
in(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);

drowley@amd3990x:~$ pgbench -n -T 60 -j 64 -c 64 -f bench.sql -P 10 postgres
Master (6d41dd045):
tps = 992586.991045 (without initial connection time)
tps = 987964.990483 (without initial connection time)
tps = 994309.670918 (without initial connection time)

Master + v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch
tps = 956022.557626 (without initial connection time)
tps = 963043.352823 (without initial connection time)
tps = 968582.600100 (without initial connection time)

This puts the patched version about 3% slower. I'm not sure how much
of that is changes in the binary and noise and how much is the
needless hashtable build done for eval_const_expressions().

I wondered if we should make it the query planner's job of deciding if
the ScalarArrayOpExpr should be hashed or not. I ended up with the
attached rough-cut patch that introduces HashedScalarArrayOpExpr and
has the query planner decide if it's going to replace
ScalarArrayOpExpr with these HashedScalarArrayOpExpr during
preprocess_expression(). I do think that we might want to consider
being a bit selective about when we do these replacements. It seems
likely that we'd want to do this for EXPRKIND_QUAL and maybe
EXPRKIND_TARGET, but I imagine that converting ScalarArrayOpExpr to
HashedScalarArrayOpExpr for EXPRKIND_VALUES would be a waste of time
since those will just be executed once.

I tried the same above test with the
v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch plus the
attached rough-cut patch and got:

master + v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch
+ v5-0002-Rough-cut-patch-for-HashedScalarArrayOpExpr.patch
tps = 1167969.983173 (without initial connection time)
tps = 1199636.793314 (without initial connection time)
tps = 1190690.939963 (without initial connection time)

I can't really explain why this became faster. I was expecting it just
to reduce that slowdown of the v4 patch a little. I don't really see
any reason why it would become faster. It's almost 20% faster which
seems like too much to just be fluctuations in code alignment in the
binary.

The attached patch is still missing the required changes to
llvmjit_expr.c. I think that was also missing from the original patch
too, however.

Also, I added HashedScalarArrayOpExpr to plannodes.h. All other Expr
type nodes are in primnodes.h. However, I put HashedScalarArrayOpExpr
in plannodes.h because the parser does not generate this and it's not
going to be stored in the catalogue files anywhere. I'm not so sure
inventing a new Expr type node that only can be generated by the
planner is a good thing to do.

Anyway, wondering what you think of the idea of allowing the planner
to choose if it's going to hash or not?

It might also be good if someone else can check if they can get a bit
more stable performance results from benchmarking the patches.

(Also attached your v4 patch again just so anyone following along at
home does not need to hunt around for the correct set of patches to
apply to test this)

David

Attachments:

v5-0002-Rough-cut-patch-for-HashedScalarArrayOpExpr.patchapplication/octet-stream; name=v5-0002-Rough-cut-patch-for-HashedScalarArrayOpExpr.patchDownload
From 136a1071f20764907d00d4f049d16585cc2d6620 Mon Sep 17 00:00:00 2001
From: "dgrowley@gmail.com" <dgrowley@gmail.com>
Date: Tue, 6 Apr 2021 00:17:57 +1200
Subject: [PATCH v5 2/2] Rough-cut patch for HashedScalarArrayOpExpr

---
 src/backend/executor/execExpr.c           | 155 +++++++++++++---------
 src/backend/executor/execExprInterp.c     |  39 +++---
 src/backend/executor/execMain.c           |   6 +
 src/backend/nodes/copyfuncs.c             |  13 ++
 src/backend/nodes/nodeFuncs.c             |  33 ++++-
 src/backend/nodes/outfuncs.c              |  11 ++
 src/backend/nodes/readfuncs.c             |  12 ++
 src/backend/optimizer/plan/planner.c      |   9 ++
 src/backend/optimizer/util/clauses.c      |  70 ++++++++++
 src/backend/utils/adt/ruleutils.c         |  32 +++++
 src/include/executor/execExpr.h           |   9 +-
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/plannodes.h             |  14 ++
 src/include/optimizer/optimizer.h         |   2 +
 src/test/regress/expected/expressions.out |   2 +-
 src/test/regress/sql/expressions.sql      |   2 +-
 16 files changed, 314 insertions(+), 96 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index ec079e7837..748baced34 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -51,7 +51,6 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
-#define MIN_ARRAY_SIZE_FOR_SAOP_HASH 9
 
 typedef struct LastAttnumInfo
 {
@@ -1146,13 +1145,11 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
-				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
-				bool		useHash = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -1165,65 +1162,103 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
+				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				func = opexpr->opfuncid;
+				fmgr_info(opexpr->opfuncid, finfo);
+				fmgr_info_set_expr((Node *) node, finfo);
+				InitFunctionCallInfoData(*fcinfo, finfo, 2,
+										 opexpr->inputcollid, NULL, NULL);
+
+				/* Evaluate scalar directly into left function argument */
+				ExecInitExprRec(scalararg, state,
+								&fcinfo->args[0].value, &fcinfo->args[0].isnull);
 
 				/*
-				 * If we have a constant array and want OR semantics, then we
-				 * implement the op with a hash lookup instead of looping
-				 * through the entire array for each execution.
+				 * Evaluate array argument into our return value.  There's no
+				 * danger in that, because the return value is guaranteed to
+				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
+				 * passed to any other expression.
 				 */
-				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
-					!((Const *) arrayarg)->constisnull)
-				{
-					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
-					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-					int			nitems;
+				ExecInitExprRec(arrayarg, state, resv, resnull);
 
-					/*
-					 * Only do the optimization if we have a large enough
-					 * array to make it worth it.
-					 */
-					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
-					if (nitems >= MIN_ARRAY_SIZE_FOR_SAOP_HASH)
-					{
-						Oid			hash_func;
+				/* And perform the operation */
+				scratch.opcode = EEOP_SCALARARRAYOP;
+				scratch.d.scalararrayop.element_type = InvalidOid;
+				scratch.d.scalararrayop.useOr = opexpr->useOr;
+				scratch.d.scalararrayop.finfo = finfo;
+				scratch.d.scalararrayop.fcinfo_data = fcinfo;
+				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
 
-						/*
-						 * Find the hash op that matches the originally planned
-						 * equality op. If we don't have one, we'll just fall
-						 * back to the default linear scan implementation.
-						 */
-						useHash = get_op_hash_functions(opexpr->opno, NULL, &hash_func);
+		case T_HashedScalarArrayOpExpr:
+			{
+				HashedScalarArrayOpExpr *hashsaop = (HashedScalarArrayOpExpr *) node;
+				ScalarArrayOpExpr *saop = hashsaop->saop;
+				Oid			func;
+				Expr	   *scalararg;
+				Const	   *array;
+				FmgrInfo   *finfo;
+				FunctionCallInfo fcinfo;
+				AclResult	aclresult;
+				Oid			hash_func;
+				FmgrInfo   *hash_finfo;
+				FunctionCallInfo hash_fcinfo;
 
-						if (useHash)
-						{
-							FmgrInfo   *hash_finfo;
-							FunctionCallInfo hash_fcinfo;
-
-							hash_finfo = palloc0(sizeof(FmgrInfo));
-							hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
-							fmgr_info(hash_func, hash_finfo);
-							fmgr_info_set_expr((Node *) node, hash_finfo);
-							InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
-													 opexpr->inputcollid, NULL, NULL);
-							InvokeFunctionExecuteHook(hash_func);
-
-							scratch.d.scalararrayhashedop.hash_finfo = hash_finfo;
-							scratch.d.scalararrayhashedop.hash_fcinfo_data = hash_fcinfo;
-							scratch.d.scalararrayhashedop.hash_fn_addr = hash_finfo->fn_addr;
-						}
-					}
-				}
+				Assert(list_length(saop->args) == 2);
+				scalararg = (Expr *) linitial(saop->args);
+				array = (Const *) lsecond(saop->args);
+
+				/* Check permission to call function */
+				aclresult = pg_proc_aclcheck(saop->opfuncid,
+											 GetUserId(),
+											 ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, OBJECT_FUNCTION,
+								   get_func_name(saop->opfuncid));
+
+				/* Set up the primary fmgr lookup information */
+				finfo = palloc0(sizeof(FmgrInfo));
+				fcinfo = palloc0(SizeForFunctionCallInfo(2));
+				func = saop->opfuncid;
+
+				/*
+				 * Make sure the planner didn't make a HashedScalarArrayOpExpr
+				 * when it shouldn't have.
+				 */
+				Assert(saop->useOr);
+				Assert(IsA(array, Const));
+				Assert(!array->constisnull);
+
+				/*
+				 * Find the hash op that matches the originally planned
+				 * equality op.
+				 */
+				if (!get_op_hash_functions(saop->opno, NULL, &hash_func))
+					elog(ERROR, "could not find hash function for hash operator %u",
+						saop->opno);
+
+				hash_finfo = palloc0(sizeof(FmgrInfo));
+				hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
+				fmgr_info(hash_func, hash_finfo);
+				fmgr_info_set_expr((Node *) node, hash_finfo);
+				InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
+										 saop->inputcollid, NULL, NULL);
+				InvokeFunctionExecuteHook(hash_func);
+
+				scratch.d.hashedscalararrayop.hash_finfo = hash_finfo;
+				scratch.d.hashedscalararrayop.hash_fcinfo_data = hash_fcinfo;
+				scratch.d.hashedscalararrayop.hash_fn_addr = hash_finfo->fn_addr;
 
 				InvokeFunctionExecuteHook(func);
 				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
-										 opexpr->inputcollid, NULL, NULL);
+										 saop->inputcollid, NULL, NULL);
 
 				/* Evaluate scalar directly into left function argument */
 				ExecInitExprRec(scalararg, state,
@@ -1232,28 +1267,16 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP[_HASHED], and will
-				 * not be passed to any other expression.
+				 * be overwritten by EEOP_HASHED_SCALARARRAYOP, and will not
+				 * be passed to any other expression.
 				 */
-				ExecInitExprRec(arrayarg, state, resv, resnull);
+				ExecInitExprRec((Expr *) array, state, resv, resnull);
 
 				/* And perform the operation */
-				if (useHash)
-				{
-					scratch.opcode = EEOP_SCALARARRAYOP_HASHED;
-					scratch.d.scalararrayhashedop.finfo = finfo;
-					scratch.d.scalararrayhashedop.fcinfo_data = fcinfo;
-					scratch.d.scalararrayhashedop.fn_addr = finfo->fn_addr;
-				}
-				else
-				{
-					scratch.opcode = EEOP_SCALARARRAYOP;
-					scratch.d.scalararrayop.element_type = InvalidOid;
-					scratch.d.scalararrayop.useOr = opexpr->useOr;
-					scratch.d.scalararrayop.finfo = finfo;
-					scratch.d.scalararrayop.fcinfo_data = fcinfo;
-					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
-				}
+				scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
+				scratch.d.hashedscalararrayop.finfo = finfo;
+				scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
+				scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d7968b299d..fd059377e3 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,7 +76,6 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
-#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -184,9 +183,9 @@ saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2);
 static uint32 saop_element_hash(struct saophash_hash *tb, Datum key);
 
 /*
- * Define parameters for ScalarArrayOpExpr hash table code generation. The interface is
- * *also* declared in execnodes.h (to generate the types, which are externally
- * visible).
+ * Define parameters for ScalarArrayOpExpr hash table code generation. The
+ * interface is *also* declared in execnodes.h (to generate the types, which
+ * are externally visible).
  */
 #define SH_PREFIX saophash
 #define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
@@ -448,7 +447,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
-		&&CASE_EEOP_SCALARARRAYOP_HASHED,
+		&&CASE_EEOP_HASHED_SCALARARRAYOP,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1459,10 +1458,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
-		EEO_CASE(EEOP_SCALARARRAYOP_HASHED)
+		EEO_CASE(EEOP_HASHED_SCALARARRAYOP)
 		{
 			/* too complex for an inline implementation */
-			ExecEvalScalarArrayOpHashed(state, op, econtext);
+			ExecEvalHashedScalarArrayOp(state, op, econtext);
 
 			EEO_NEXT();
 		}
@@ -3386,13 +3385,13 @@ static uint32
 saop_element_hash(struct saophash_hash *tb, Datum key)
 {
 	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
-	FunctionCallInfo fcinfo = elements_tab->op->d.scalararrayhashedop.hash_fcinfo_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.hashedscalararrayop.hash_fcinfo_data;
 	Datum hash;
 
 	fcinfo->args[0].value = key;
 	fcinfo->args[0].isnull = false;
 
-	hash = elements_tab->op->d.scalararrayhashedop.hash_fn_addr(fcinfo);
+	hash = elements_tab->op->d.hashedscalararrayop.hash_fn_addr(fcinfo);
 
 	return DatumGetUInt32(hash);
 }
@@ -3407,14 +3406,14 @@ saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2)
 	Datum result;
 
 	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
-	FunctionCallInfo fcinfo = elements_tab->op->d.scalararrayhashedop.fcinfo_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.hashedscalararrayop.fcinfo_data;
 
 	fcinfo->args[0].value = key1;
 	fcinfo->args[0].isnull = false;
 	fcinfo->args[1].value = key2;
 	fcinfo->args[1].isnull = false;
 
-	result = elements_tab->op->d.scalararrayhashedop.fn_addr(fcinfo);
+	result = elements_tab->op->d.hashedscalararrayop.fn_addr(fcinfo);
 
 	return DatumGetBool(result);
 }
@@ -3432,11 +3431,11 @@ saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2)
  * The operator always yields boolean.
  */
 void
-ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
-	ScalarArrayOpExprHashTable elements_tab = op->d.scalararrayhashedop.elements_tab;
-	FunctionCallInfo fcinfo = op->d.scalararrayhashedop.fcinfo_data;
-	bool		strictfunc = op->d.scalararrayhashedop.finfo->fn_strict;
+	ScalarArrayOpExprHashTable elements_tab = op->d.hashedscalararrayop.elements_tab;
+	FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
+	bool		strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
 	ArrayType  *arr;
 	Datum		scalar = fcinfo->args[0].value;
 	bool		scalar_isnull = fcinfo->args[0].isnull;
@@ -3457,7 +3456,7 @@ ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *eco
 		return;
 	}
 
-	/* Preprocess the array the first time we execute the op. */
+	/* Build the hash table on first evaluation */
 	if (elements_tab == NULL)
 	{
 		int16		typlen;
@@ -3481,7 +3480,7 @@ ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *eco
 		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 
 		elements_tab = (ScalarArrayOpExprHashTable) palloc(sizeof(ScalarArrayOpExprHashTableData));
-		op->d.scalararrayhashedop.elements_tab = elements_tab;
+		op->d.hashedscalararrayop.elements_tab = elements_tab;
 		elements_tab->op = op;
 		elements_tab->hashtab = saophash_create(CurrentMemoryContext, nitems, elements_tab);
 
@@ -3524,7 +3523,7 @@ ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *eco
 		 * Remember if we had any nulls so that we know if we need to execute
 		 * non-strict functions with a null lhs value if no match is found.
 		 */
-		op->d.scalararrayhashedop.has_nulls = num_nulls > 0;
+		op->d.hashedscalararrayop.has_nulls = num_nulls > 0;
 
 		/*
 		 * We only setup a binary search op if we have > 8 elements, so we don't
@@ -3544,7 +3543,7 @@ ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	 * the possibility of null values (we've previously removed them from the
 	 * array).
 	 */
-	if (!DatumGetBool(result) && op->d.scalararrayhashedop.has_nulls)
+	if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
 	{
 		if (strictfunc)
 		{
@@ -3570,7 +3569,7 @@ ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *eco
 			fcinfo->args[1].value = (Datum) 0;
 			fcinfo->args[1].isnull = true;
 
-			result = op->d.scalararrayhashedop.fn_addr(fcinfo);
+			result = op->d.hashedscalararrayop.fn_addr(fcinfo);
 			resultnull = fcinfo->isnull;
 		}
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 163242f54e..e5c230109b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1624,6 +1624,12 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 			Expr	   *checkconstr;
 
 			checkconstr = stringToNode(check[i].ccbin);
+			/*
+			 * XXX consider passing checkconstr through
+			 * convert_saop_to_hashed_saop?  We'd need to only do this when
+			 * performing bulk inserts, and there does not currently seem to
+			 * be any way to determine that.
+			 */
 			resultRelInfo->ri_ConstraintExprs[i] =
 				ExecPrepareExpr(checkconstr, estate);
 		}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ad729d10a8..f6197f485a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1276,6 +1276,16 @@ _copyPlanRowMark(const PlanRowMark *from)
 	return newnode;
 }
 
+static HashedScalarArrayOpExpr *
+_copyHashedScalarArrayOpExpr(const HashedScalarArrayOpExpr *from)
+{
+	HashedScalarArrayOpExpr *newnode = makeNode(HashedScalarArrayOpExpr);
+
+	COPY_NODE_FIELD(saop);
+
+	return newnode;
+}
+
 static PartitionPruneInfo *
 _copyPartitionPruneInfo(const PartitionPruneInfo *from)
 {
@@ -5091,6 +5101,9 @@ copyObjectImpl(const void *from)
 		case T_PlanRowMark:
 			retval = _copyPlanRowMark(from);
 			break;
+		case T_HashedScalarArrayOpExpr:
+			retval = _copyHashedScalarArrayOpExpr(from);
+			break;
 		case T_PartitionPruneInfo:
 			retval = _copyPartitionPruneInfo(from);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 860e9a2a06..82cd29bb2c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -86,6 +86,9 @@ exprType(const Node *expr)
 		case T_ScalarArrayOpExpr:
 			type = BOOLOID;
 			break;
+		case T_HashedScalarArrayOpExpr:
+			type = BOOLOID;
+			break;
 		case T_BoolExpr:
 			type = BOOLOID;
 			break;
@@ -802,10 +805,13 @@ exprCollation(const Node *expr)
 			coll = ((const NullIfExpr *) expr)->opcollid;
 			break;
 		case T_ScalarArrayOpExpr:
-			coll = InvalidOid;	/* result is always boolean */
+			coll = InvalidOid;	/* result is always InvalidOid */
+			break;
+		case T_HashedScalarArrayOpExpr:
+			coll = InvalidOid;	/* result is always InvalidOid */
 			break;
 		case T_BoolExpr:
-			coll = InvalidOid;	/* result is always boolean */
+			coll = InvalidOid;	/* result is always InvalidOid */
 			break;
 		case T_SubLink:
 			{
@@ -1050,10 +1056,10 @@ exprSetCollation(Node *expr, Oid collation)
 			((NullIfExpr *) expr)->opcollid = collation;
 			break;
 		case T_ScalarArrayOpExpr:
-			Assert(!OidIsValid(collation)); /* result is always boolean */
+			Assert(!OidIsValid(collation)); /* always InvalidOid */
 			break;
 		case T_BoolExpr:
-			Assert(!OidIsValid(collation)); /* result is always boolean */
+			Assert(!OidIsValid(collation)); /* always InvalidOid */
 			break;
 		case T_SubLink:
 #ifdef USE_ASSERT_CHECKING
@@ -2012,6 +2018,15 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_HashedScalarArrayOpExpr:
+			{
+				HashedScalarArrayOpExpr *expr = (HashedScalarArrayOpExpr *) node;
+
+				if (expression_tree_walker((Node *) expr->saop,
+					walker, context))
+					return true;
+			}
+			break;
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
@@ -2777,6 +2792,16 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_HashedScalarArrayOpExpr:
+			{
+				HashedScalarArrayOpExpr *expr = (HashedScalarArrayOpExpr *) node;
+				HashedScalarArrayOpExpr *newnode;
+
+				FLATCOPY(newnode, expr, HashedScalarArrayOpExpr);
+				MUTATE(newnode->saop, expr->saop, ScalarArrayOpExpr *);
+				return (Node *) newnode;
+			}
+			break;
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index fa8f65fbc5..74789485ee 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -987,6 +987,14 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
 	WRITE_BOOL_FIELD(isParent);
 }
 
+static void
+_outHashedScalarArrayOpExpr(StringInfo str, const HashedScalarArrayOpExpr *node)
+{
+	WRITE_NODE_TYPE("HASHEDSCALARARRAYOPEXPR");
+
+	WRITE_NODE_FIELD(saop);
+}
+
 static void
 _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 {
@@ -3968,6 +3976,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanRowMark:
 				_outPlanRowMark(str, obj);
 				break;
+			case T_HashedScalarArrayOpExpr:
+				_outHashedScalarArrayOpExpr(str, obj);
+				break;
 			case T_PartitionPruneInfo:
 				_outPartitionPruneInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ecce23b747..bc4dfe0ce7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2523,6 +2523,16 @@ _readPlanRowMark(void)
 	READ_DONE();
 }
 
+static HashedScalarArrayOpExpr *
+_readHashedScalarArrayOpExpr(void)
+{
+	READ_LOCALS(HashedScalarArrayOpExpr);
+
+	READ_NODE_FIELD(saop);
+
+	READ_DONE();
+}
+
 static PartitionPruneInfo *
 _readPartitionPruneInfo(void)
 {
@@ -2949,6 +2959,8 @@ parseNodeString(void)
 		return_value = _readNestLoopParam();
 	else if (MATCH("PLANROWMARK", 11))
 		return_value = _readPlanRowMark();
+	else if (MATCH("HASHEDSCALARARRAYOPEXPR", 23))
+		return_value = _readHashedScalarArrayOpExpr();
 	else if (MATCH("PARTITIONPRUNEINFO", 18))
 		return_value = _readPartitionPruneInfo();
 	else if (MATCH("PARTITIONEDRELPRUNEINFO", 23))
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 898d7fcb0b..0ccb7638c2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1110,6 +1110,15 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 #endif
 	}
 
+	/*
+	 * Check for ScalarArrayOpExpr and check if any can be converted into
+	 * HashedScalarArrayOpExpr for increased repeat evaulation performance.
+	 */
+	if (kind == EXPRKIND_QUAL || kind == EXPRKIND_TARGET)
+	{
+		expr = convert_saop_to_hashed_saop(expr);
+	}
+
 	/* Expand SubLinks to SubPlans */
 	if (root->parse->hasSubLinks)
 		expr = SS_process_sublinks(root, expr, (kind == EXPRKIND_QUAL));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index bea1cc4d67..bbd6954448 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -106,6 +106,7 @@ static bool contain_leaked_vars_walker(Node *node, void *context);
 static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static List *find_nonnullable_vars_walker(Node *node, bool top_level);
 static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
+static Node *convert_saop_to_hashed_saop_mutator(Node *node, void *context);
 static Node *eval_const_expressions_mutator(Node *node,
 											eval_const_expressions_context *context);
 static bool contain_non_const_walker(Node *node, void *context);
@@ -2101,6 +2102,75 @@ eval_const_expressions(PlannerInfo *root, Node *node)
 	return eval_const_expressions_mutator(node, &context);
 }
 
+#define MIN_ARRAY_SIZE_FOR_HASHED_SAOP 9
+/*--------------------
+ * convert_saop_to_hashed_saop
+ *
+ * Recursively search 'node' for ScalarArrayOpExprs and convert any eligible
+ * ScalarArrayOpExprs into HashedScalarArrayOpExpr.
+ *
+ * The ScalarArrayOpExpr is eligible for conversion if:
+ * 1. The 2nd argument of the array does not contain any Vars, Params or
+ *	  volatile functions.
+ * 2. There's valid hash function for the given type.
+ * 3. If the array contains enough elements for us to consider it to be
+ *	  worthwhile using the hashed version of ScalarArrayOpExprs rather than
+ *	  the traditional version.
+ */
+Node *
+convert_saop_to_hashed_saop(Node *node)
+{
+	return convert_saop_to_hashed_saop_mutator(node, NULL);
+}
+
+static Node *
+convert_saop_to_hashed_saop_mutator(Node *node, void *context)
+{
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Expr	   *arrayarg = (Expr *) lsecond(saop->args);
+
+		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
+			!((Const *) arrayarg)->constisnull &&
+			op_hashjoinable(saop->opno, exprType((Node *) arrayarg)))
+		{
+			Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+			ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+			int			nitems;
+
+			/*
+			 * Only do the conversion if we have a large enough array to make
+			 * hashing worthwhile.
+			 */
+			nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+			if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+			{
+				HashedScalarArrayOpExpr *hashedsaop;
+
+				/*
+				 * HashedScalarArrayOpExpr is a simple wrapper around
+				 * ScalarArrayOpExpr to inform the executor to build a hash
+				 * table for the ScalarArrayOpExpr.
+				 */
+				hashedsaop = makeNode(HashedScalarArrayOpExpr);
+				hashedsaop->saop = saop;
+
+				return (Node *) hashedsaop;
+			}
+		}
+
+		return (Node *) saop;
+	}
+
+	return expression_tree_mutator(node, convert_saop_to_hashed_saop, NULL);
+}
+
+
 /*--------------------
  * estimate_expression_value
  *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 254e8f3050..8fc19fb381 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8330,6 +8330,38 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_HashedScalarArrayOpExpr:
+		{
+			HashedScalarArrayOpExpr *hsaop = (HashedScalarArrayOpExpr *) node;
+			ScalarArrayOpExpr *expr = hsaop->saop;
+			List	   *args = expr->args;
+			Node	   *arg1 = (Node *) linitial(args);
+			Node	   *arg2 = (Node *) lsecond(args);
+
+			Assert(expr->useOr);
+
+			if (!PRETTY_PAREN(context))
+				appendStringInfoChar(buf, '(');
+			get_rule_expr_paren(arg1, context, true, node);
+
+			/*
+			 * XXX Syntax: Should this look like a ScalarArrayOpExpr or
+			 * should we make it look slightly different? Or should this
+			 * output always be reparseable?
+			 */
+			appendStringInfo(buf, " %s %s (",
+				generate_operator_name(expr->opno,
+					exprType(arg1),
+					get_base_element_type(exprType(arg2))), "HASH ANY");
+			get_rule_expr_paren(arg2, context, true, node);
+
+			Assert(!IsA(arg2, SubLink));
+			appendStringInfoChar(buf, ')');
+			if (!PRETTY_PAREN(context))
+				appendStringInfoChar(buf, ')');
+		}
+		break;
+
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index dc298e3597..88e85db52c 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -242,7 +242,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
-	EEOP_SCALARARRAYOP_HASHED,
+	EEOP_HASHED_SCALARARRAYOP,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -579,7 +579,7 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
-		/* for EEOP_SCALARARRAYOP_HASHED */
+		/* for EEOP_HASHED_SCALARARRAYOP */
 		struct
 		{
 			bool		has_nulls;
@@ -592,7 +592,7 @@ typedef struct ExprEvalStep
 			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
 			/* faster to access without additional indirection: */
 			PGFunction	hash_fn_addr;	/* actual call address */
-		}			scalararrayhashedop;
+		}			hashedscalararrayop;
 
 		/* for EEOP_XMLEXPR */
 		struct
@@ -765,7 +765,8 @@ extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
-extern void ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+extern void ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op,
+										ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2051abbbf9..547f9c94c0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -90,6 +90,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Plan: */
 	T_NestLoopParam,
 	T_PlanRowMark,
+	T_HashedScalarArrayOpExpr,
 	T_PartitionPruneInfo,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1678bd66fe..fb2cbf471f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1124,6 +1124,20 @@ typedef struct PlanRowMark
 	bool		isParent;		/* true if this is a "dummy" parent entry */
 } PlanRowMark;
 
+/*
+ * HashedScalarArrayOpExpr
+ *	   Hashed version of a ScalarArrayOpExpr
+ *
+ * These are generated by convert_saop_to_hashed_saop() in order to allow
+ * faster repeat evaluation of ScalarArrayOpExpr by using a hash table when
+ * it's applicable to do so.  These are never generated during parse, hence
+ * this struct is here rather than in primnodes.h.
+ */
+typedef struct HashedScalarArrayOpExpr
+{
+	NodeTag		type;
+	ScalarArrayOpExpr	*saop;
+} HashedScalarArrayOpExpr;
 
 /*
  * Node types to represent partition pruning information.
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..813801dfb1 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -146,6 +146,8 @@ extern bool contain_volatile_functions_not_nextval(Node *clause);
 
 extern Node *eval_const_expressions(PlannerInfo *root, Node *node);
 
+extern Node *convert_saop_to_hashed_saop(Node *node);
+
 extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
 extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 42e1c0d1f2..8d66904d69 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -159,7 +159,7 @@ select count(*) from date_tbl
 (1 row)
 
 --
--- Tests for ScalarArrayOpExpr hash optimization
+-- Tests for HashedScalarArrayOpExpr
 --
 select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
  ?column? 
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index ef2738067b..5a4689e264 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -67,7 +67,7 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 
 --
--- Tests for ScalarArrayOpExpr hash optimization
+-- Tests for HashedScalarArrayOpExpr
 --
 
 select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
-- 
2.21.0.windows.1

v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patchapplication/octet-stream; name=v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patchDownload
From c68fe7488be6d93f29a2f2bdef91da7c8fd357ef Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Fri, 10 Apr 2020 21:40:50 +0000
Subject: [PATCH v4] Hash lookup const arrays in OR'd ScalarArrayOps

Currently all scalar array op expressions execute as a linear search
through the array argument. However when OR semantics are desired it's
possible to instead use a hash lookup. Here we apply that optimization
to constant arrays (so we don't need to worry about teaching expression
execution when params change) of at least length 9 (since very short
arrays average to the same number of comparisons for linear searches and
thus avoid the preprocessing necessary to build the hash).
---
 src/backend/executor/execExpr.c           |  84 +++++++-
 src/backend/executor/execExprInterp.c     | 234 ++++++++++++++++++++++
 src/include/executor/execExpr.h           |  41 ++++
 src/test/regress/expected/expressions.out |  45 +++++
 src/test/regress/sql/expressions.sql      |  12 ++
 5 files changed, 406 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2e463f5499..4b9714f071 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -31,6 +31,7 @@
 #include "postgres.h"
 
 #include "access/nbtree.h"
+#include "access/hash.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_type.h"
 #include "executor/execExpr.h"
@@ -50,6 +51,7 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
+#define MIN_ARRAY_SIZE_FOR_SAOP_HASH 9
 
 typedef struct LastAttnumInfo
 {
@@ -944,11 +946,13 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Oid			func;
 				Expr	   *scalararg;
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				bool		useHash = false;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -961,12 +965,62 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				func = opexpr->opfuncid;
+
+				/*
+				 * If we have a constant array and want OR semantics, then we
+				 * implement the op with a hash lookup instead of looping
+				 * through the entire array for each execution.
+				 */
+				if (opexpr->useOr && arrayarg && IsA(arrayarg, Const) &&
+					!((Const *) arrayarg)->constisnull)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
+
+					/*
+					 * Only do the optimization if we have a large enough
+					 * array to make it worth it.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					if (nitems >= MIN_ARRAY_SIZE_FOR_SAOP_HASH)
+					{
+						Oid			hash_func;
+
+						/*
+						 * Find the hash op that matches the originally planned
+						 * equality op. If we don't have one, we'll just fall
+						 * back to the default linear scan implementation.
+						 */
+						useHash = get_op_hash_functions(opexpr->opno, NULL, &hash_func);
+
+						if (useHash)
+						{
+							FmgrInfo   *hash_finfo;
+							FunctionCallInfo hash_fcinfo;
+
+							hash_finfo = palloc0(sizeof(FmgrInfo));
+							hash_fcinfo = palloc0(SizeForFunctionCallInfo(2));
+							fmgr_info(hash_func, hash_finfo);
+							fmgr_info_set_expr((Node *) node, hash_finfo);
+							InitFunctionCallInfoData(*hash_fcinfo, hash_finfo, 2,
+													 opexpr->inputcollid, NULL, NULL);
+							InvokeFunctionExecuteHook(hash_func);
+
+							scratch.d.scalararrayhashedop.hash_finfo = hash_finfo;
+							scratch.d.scalararrayhashedop.hash_fcinfo_data = hash_fcinfo;
+							scratch.d.scalararrayhashedop.hash_fn_addr = hash_finfo->fn_addr;
+						}
+					}
+				}
+
+				InvokeFunctionExecuteHook(func);
+				fmgr_info(func, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -978,18 +1032,28 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/*
 				 * Evaluate array argument into our return value.  There's no
 				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * be overwritten by EEOP_SCALARARRAYOP[_HASHED], and will
+				 * not be passed to any other expression.
 				 */
 				ExecInitExprRec(arrayarg, state, resv, resnull);
 
 				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				if (useHash)
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP_HASHED;
+					scratch.d.scalararrayhashedop.finfo = finfo;
+					scratch.d.scalararrayhashedop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayhashedop.fn_addr = finfo->fn_addr;
+				}
+				else
+				{
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				}
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 6308286d8c..d7968b299d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -76,6 +76,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "lib/qunique.h"
 
 /*
  * Use computed-goto-based opcode dispatch when computed gotos are available.
@@ -178,6 +179,27 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate
 															  ExprContext *aggcontext,
 															  int setno);
 
+static bool
+saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2);
+static uint32 saop_element_hash(struct saophash_hash *tb, Datum key);
+
+/*
+ * Define parameters for ScalarArrayOpExpr hash table code generation. The interface is
+ * *also* declared in execnodes.h (to generate the types, which are externally
+ * visible).
+ */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
+#define SH_KEY_TYPE Datum
+#define SH_KEY key
+#define SH_HASH_KEY(tb, key) saop_element_hash(tb, key)
+#define SH_EQUAL(tb, a, b) saop_hash_element_match(tb, a, b)
+#define SH_SCOPE extern
+#define SH_STORE_HASH
+#define SH_GET_HASH(tb, a) a->hash
+#define SH_DEFINE
+#include "lib/simplehash.h"
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -426,6 +448,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_SCALARARRAYOP_HASHED,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1436,6 +1459,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_SCALARARRAYOP_HASHED)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOpHashed(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3345,6 +3376,209 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Hash function for scalar array hash op elements.
+ *
+ * We use the element type's default hash opclass, and the column collation
+ * if the type is collation-sensitive.
+ */
+static uint32
+saop_element_hash(struct saophash_hash *tb, Datum key)
+{
+	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.scalararrayhashedop.hash_fcinfo_data;
+	Datum hash;
+
+	fcinfo->args[0].value = key;
+	fcinfo->args[0].isnull = false;
+
+	hash = elements_tab->op->d.scalararrayhashedop.hash_fn_addr(fcinfo);
+
+	return DatumGetUInt32(hash);
+}
+
+/*
+ * Matching function for scalar array hash op elements, to be used in hashtable
+ * lookups.
+ */
+static bool
+saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2)
+{
+	Datum result;
+
+	ScalarArrayOpExprHashTable elements_tab = (ScalarArrayOpExprHashTable) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.scalararrayhashedop.fcinfo_data;
+
+	fcinfo->args[0].value = key1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = key2;
+	fcinfo->args[1].isnull = false;
+
+	result = elements_tab->op->d.scalararrayhashedop.fn_addr(fcinfo);
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * This is an optimized version of ExecEvalScalarArrayOp that only supports
+ * ANY (i.e., OR semantics) because it performs a hashtable lookup to determine
+ * if the array contains the value.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean.
+ */
+void
+ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ScalarArrayOpExprHashTable elements_tab = op->d.scalararrayhashedop.elements_tab;
+	FunctionCallInfo fcinfo = op->d.scalararrayhashedop.fcinfo_data;
+	bool		strictfunc = op->d.scalararrayhashedop.finfo->fn_strict;
+	ArrayType  *arr;
+	Datum		scalar = fcinfo->args[0].value;
+	bool		scalar_isnull = fcinfo->args[0].isnull;
+	Datum		result;
+	bool		resultnull;
+	bool		hashfound;
+
+	/* We don't setup a hashed scalar array op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Preprocess the array the first time we execute the op. */
+	if (elements_tab == NULL)
+	{
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+		int			nitems;
+		int			num_nulls = 0;
+		char	   *s;
+		bits8	   *bitmap;
+		int			bitmask;
+		MemoryContext oldcontext;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+		nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+		elements_tab = (ScalarArrayOpExprHashTable) palloc(sizeof(ScalarArrayOpExprHashTableData));
+		op->d.scalararrayhashedop.elements_tab = elements_tab;
+		elements_tab->op = op;
+		elements_tab->hashtab = saophash_create(CurrentMemoryContext, nitems, elements_tab);
+
+		MemoryContextSwitchTo(oldcontext);
+
+		s = (char *) ARR_DATA_PTR(arr);
+		bitmap = ARR_NULLBITMAP(arr);
+		bitmask = 1;
+		for (int i = 0; i < nitems; i++)
+		{
+			Datum		element;
+
+			/* Get array element, checking for NULL. */
+			if (bitmap && (*bitmap & bitmask) == 0)
+			{
+				num_nulls++;
+			}
+			else
+			{
+				element = fetch_att(s, typbyval, typlen);
+				s = att_addlength_pointer(s, typlen, s);
+				s = (char *) att_align_nominal(s, typalign);
+
+				saophash_insert(elements_tab->hashtab, element, &hashfound);
+			}
+
+			/* Advance bitmap pointer if any. */
+			if (bitmap)
+			{
+				bitmask <<= 1;
+				if (bitmask == 0x100)
+				{
+					bitmap++;
+					bitmask = 1;
+				}
+			}
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.scalararrayhashedop.has_nulls = num_nulls > 0;
+
+		/*
+		 * We only setup a binary search op if we have > 8 elements, so we don't
+		 * need to worry about adding an optimization for the empty array case.
+		 */
+		Assert(nitems > 0);
+	}
+
+	/* Check the hash to see if we have a match. */
+	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
+
+	result = BoolGetDatum(hashfound);
+	resultnull = false;
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values (we've previously removed them from the
+	 * array).
+	 */
+	if (!DatumGetBool(result) && op->d.scalararrayhashedop.has_nulls)
+	{
+		if (strictfunc)
+		{
+			/*
+			 * Had nulls and is a strict function, so instead of executing the
+			 * function onces with a null rhs, we can assume null.
+			 */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/* TODO: No regression test for this branch. */
+			/*
+			 * Execute function will null rhs just once.
+			 *
+			 * The hash lookup path will have scribbled on the lhs argument so
+			 * we need to set it up also (even though we entered this function
+			 * with it already set).
+			 */
+			fcinfo->args[0].value = scalar;
+			fcinfo->args[0].isnull = scalar_isnull;
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			result = op->d.scalararrayhashedop.fn_addr(fcinfo);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 1b7f9865b0..dc298e3597 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -21,6 +21,30 @@
 struct ExprEvalStep;
 struct SubscriptingRefState;
 
+typedef struct ScalarArrayOpExprHashEntryData *ScalarArrayOpExprHashEntry;
+typedef struct ScalarArrayOpExprHashTableData *ScalarArrayOpExprHashTable;
+
+typedef struct ScalarArrayOpExprHashEntryData
+{
+	Datum key;
+	uint32		status;			/* hash status */
+	uint32		hash;			/* hash value (cached) */
+} ScalarArrayOpExprHashEntryData;
+
+/* define parameters necessary to generate the tuple hash table interface */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntryData
+#define SH_KEY_TYPE Datum
+#define SH_SCOPE extern
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+typedef struct ScalarArrayOpExprHashTableData
+{
+	saophash_hash *hashtab;	/* underlying hash table */
+	struct ExprEvalStep *op;
+}			ScalarArrayOpExprHashTableData;
+
 /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
 /* expression's interpreter has been initialized */
 #define EEO_FLAG_INTERPRETER_INITIALIZED	(1 << 1)
@@ -218,6 +242,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_SCALARARRAYOP_HASHED,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -554,6 +579,21 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_SCALARARRAYOP_HASHED */
+		struct
+		{
+			bool		has_nulls;
+			ScalarArrayOpExprHashTable	   elements_tab;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+			FmgrInfo   *hash_finfo;	/* function's lookup data */
+			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	hash_fn_addr;	/* actual call address */
+		}			scalararrayhashedop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -725,6 +765,7 @@ extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalScalarArrayOpHashed(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 05a6eb07b2..42e1c0d1f2 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,48 @@ select count(*) from date_tbl
     13
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr hash optimization
+--
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ t
+(1 row)
+
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..ef2738067b 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,15 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr hash optimization
+--
+
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 1 in (null, null, null, null, null, null, null, null, null, null, null);
+select 1 in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select null::int in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select 'a' in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
-- 
2.20.1

#46James Coleman
jtc331@gmail.com
In reply to: David Rowley (#45)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Mon, Apr 5, 2021 at 11:58 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, 20 Mar 2021 at 09:41, James Coleman <jtc331@gmail.com> wrote:

I've attached a cleaned up patch. Last CF it was listed in is
https://commitfest.postgresql.org/29/2542/ -- what's the appropriate
step to take here given it's an already existing patch, but not yet
moved into recent CFs?

I had a look at this patch. I like the idea of using a simplehash.h
hash table to hash the constant values so that repeat lookups can be
performed much more quickly, however, I'm a bit concerned that there
are quite a few places in the code where we often just execute a
ScalarArrayOpExpr once and I'm a bit worried that we'll slow down
expression evaluation of those cases.

The two cases that I have in mind are:

1. eval_const_expressions() where we use the executor to evaluate the
ScalarArrayOpExpr to see if the result is Const.
2. CHECK constraints with IN clauses and single-row INSERTs.

This is a good point I hadn't considered; now that you mention it, I
think another case would be expression evaluation in pl/pgsql.

I tried to benchmark both of these but I'm struggling to get stable
enough performance for #2, even with fsync=off. Sometimes I'm getting
results 2.5x slower than other runs.

For benchmarking #1 I'm also not too sure I'm getting stable enough
results for them to mean anything.

I was running:

create table a (a int);

bench.sql: explain select * from a where a
in(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);

drowley@amd3990x:~$ pgbench -n -T 60 -j 64 -c 64 -f bench.sql -P 10 postgres
Master (6d41dd045):
tps = 992586.991045 (without initial connection time)
tps = 987964.990483 (without initial connection time)
tps = 994309.670918 (without initial connection time)

Master + v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch
tps = 956022.557626 (without initial connection time)
tps = 963043.352823 (without initial connection time)
tps = 968582.600100 (without initial connection time)

This puts the patched version about 3% slower. I'm not sure how much
of that is changes in the binary and noise and how much is the
needless hashtable build done for eval_const_expressions().

I wondered if we should make it the query planner's job of deciding if
the ScalarArrayOpExpr should be hashed or not. I ended up with the
attached rough-cut patch that introduces HashedScalarArrayOpExpr and
has the query planner decide if it's going to replace
ScalarArrayOpExpr with these HashedScalarArrayOpExpr during
preprocess_expression(). I do think that we might want to consider
being a bit selective about when we do these replacements. It seems
likely that we'd want to do this for EXPRKIND_QUAL and maybe
EXPRKIND_TARGET, but I imagine that converting ScalarArrayOpExpr to
HashedScalarArrayOpExpr for EXPRKIND_VALUES would be a waste of time
since those will just be executed once.

In theory we might want to cost them differently as well, though I'm
slightly hesitant to do so at this point to avoid causing plan changes
(I'm not sure how we would balance that concern with the potential
that the best plan isn't chosen).

I tried the same above test with the
v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch plus the
attached rough-cut patch and got:

master + v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch
+ v5-0002-Rough-cut-patch-for-HashedScalarArrayOpExpr.patch
tps = 1167969.983173 (without initial connection time)
tps = 1199636.793314 (without initial connection time)
tps = 1190690.939963 (without initial connection time)

I can't really explain why this became faster. I was expecting it just
to reduce that slowdown of the v4 patch a little. I don't really see
any reason why it would become faster. It's almost 20% faster which
seems like too much to just be fluctuations in code alignment in the
binary.

I'm not at a place where I can do good perf testing right now (just on
my laptop for the moment), unfortunately, so I can't confirm one way
or the other.

The attached patch is still missing the required changes to
llvmjit_expr.c. I think that was also missing from the original patch
too, however.

Ah, I didn't realize that needed to be changed as well. I'll take a
look at that.

Also, I added HashedScalarArrayOpExpr to plannodes.h. All other Expr
type nodes are in primnodes.h. However, I put HashedScalarArrayOpExpr
in plannodes.h because the parser does not generate this and it's not
going to be stored in the catalogue files anywhere. I'm not so sure
inventing a new Expr type node that only can be generated by the
planner is a good thing to do.

I don't know what the positives and negatives are of this.

Anyway, wondering what you think of the idea of allowing the planner
to choose if it's going to hash or not?

In general I think it's very reasonable. I kinda wonder if
HashedScalarArrayOpExpr should have the ScalarArrayOpExp inlined
instead of maintaining a pointer, but it's not a big deal to me either
way. It certainly adds additional code, but probably also makes the
execExpr code clearer.

It might also be good if someone else can check if they can get a bit
more stable performance results from benchmarking the patches.

(Also attached your v4 patch again just so anyone following along at
home does not need to hunt around for the correct set of patches to
apply to test this)

A few other comments:

- It looks like several of the "result is always InvalidOid" changes
should get committed separately (and now)?
- Two comment tweaks:

+ * 1. The 2nd argument of the array does not contain any Vars, Params or

s/array/array op/

+ * worthwhile using the hashed version of ScalarArrayOpExprs rather than

s/ScalarArrayOpExprs/ScalarArrayOpExpr/

- Using op_hashjoinable is an improvement over my initial patch.
- I like the name change to put HASHED/Hashed first.

Thanks,
James

#47Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: David Rowley (#45)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 4/6/21 5:58 AM, David Rowley wrote:

On Sat, 20 Mar 2021 at 09:41, James Coleman <jtc331@gmail.com> wrote:

I've attached a cleaned up patch. Last CF it was listed in is
https://commitfest.postgresql.org/29/2542/ -- what's the appropriate
step to take here given it's an already existing patch, but not yet
moved into recent CFs?

I had a look at this patch. I like the idea of using a simplehash.h
hash table to hash the constant values so that repeat lookups can be
performed much more quickly, however, I'm a bit concerned that there
are quite a few places in the code where we often just execute a
ScalarArrayOpExpr once and I'm a bit worried that we'll slow down
expression evaluation of those cases.

The two cases that I have in mind are:

1. eval_const_expressions() where we use the executor to evaluate the
ScalarArrayOpExpr to see if the result is Const.
2. CHECK constraints with IN clauses and single-row INSERTs.

I tried to benchmark both of these but I'm struggling to get stable
enough performance for #2, even with fsync=off. Sometimes I'm getting
results 2.5x slower than other runs.

For benchmarking #1 I'm also not too sure I'm getting stable enough
results for them to mean anything.

I was running:

create table a (a int);

bench.sql: explain select * from a where a
in(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);

drowley@amd3990x:~$ pgbench -n -T 60 -j 64 -c 64 -f bench.sql -P 10 postgres
Master (6d41dd045):
tps = 992586.991045 (without initial connection time)
tps = 987964.990483 (without initial connection time)
tps = 994309.670918 (without initial connection time)

Master + v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch
tps = 956022.557626 (without initial connection time)
tps = 963043.352823 (without initial connection time)
tps = 968582.600100 (without initial connection time)

This puts the patched version about 3% slower. I'm not sure how much
of that is changes in the binary and noise and how much is the
needless hashtable build done for eval_const_expressions().

I wondered if we should make it the query planner's job of deciding if
the ScalarArrayOpExpr should be hashed or not. I ended up with the
attached rough-cut patch that introduces HashedScalarArrayOpExpr and
has the query planner decide if it's going to replace
ScalarArrayOpExpr with these HashedScalarArrayOpExpr during
preprocess_expression(). I do think that we might want to consider
being a bit selective about when we do these replacements. It seems
likely that we'd want to do this for EXPRKIND_QUAL and maybe
EXPRKIND_TARGET, but I imagine that converting ScalarArrayOpExpr to
HashedScalarArrayOpExpr for EXPRKIND_VALUES would be a waste of time
since those will just be executed once.

I tried the same above test with the
v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch plus the
attached rough-cut patch and got:

master + v4-0001-Hash-lookup-const-arrays-in-OR-d-ScalarArrayOps.patch
+ v5-0002-Rough-cut-patch-for-HashedScalarArrayOpExpr.patch
tps = 1167969.983173 (without initial connection time)
tps = 1199636.793314 (without initial connection time)
tps = 1190690.939963 (without initial connection time)

I can't really explain why this became faster. I was expecting it just
to reduce that slowdown of the v4 patch a little. I don't really see
any reason why it would become faster. It's almost 20% faster which
seems like too much to just be fluctuations in code alignment in the
binary.

Interesting. I tried this on the "small" machine I use for benchmarking,
with the same SQL script you used, and also with IN() containing 10 and
100 values - so less/more than your script, which used 16 values.

I only ran that with a single client, the machine only has 4 cores and
this should not be related to concurrency, so 1 client seems fine. The
average of 10 runs, 15 seconds each look like this:

simple prepared 10/s 10/p 100/s 100/p
-------------------------------------------------------------
master 21847 59476 23343 59380 11757 56488
v4 21546 57757 22864 57704 11572 57350
v4+v5 23374 56089 24410 56140 14765 55302

The first two columns are your bench.sql, with -M simple or prepared.
The other columns are 10 or 100 values, /s is simple, /p is prepared.

Compared to master:

simple prepared 10/s 10/p 100/s 100/p
-------------------------------------------------------------
v4 98.62% 97.11% 97.95% 97.18% 98.43% 101.52%
v4+v5 106.99% 94.31% 104.57% 94.54% 125.59% 97.90%

That seems to mostly match your observation - there's a small
performance hit (~2%), although that might be due to changes in the
layout of the binary. And v4+v5 improves that a bit (even compared to
master), although I don't see the same 20% speedup.

I see +25% improvement, but only with 100 values.

It's a bit strange that in prepared mode, the v5 actually hurts the
performance a bit.

That being said, this is a pretty extreme test case. I'm pretty sure
that once the table is not empty, the results will probably show a clear
improvement. I'll collect some of those results.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#48David Rowley
dgrowleyml@gmail.com
In reply to: Tomas Vondra (#47)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, 8 Apr 2021 at 05:54, Tomas Vondra <tomas.vondra@enterprisedb.com> wrote:

I only ran that with a single client, the machine only has 4 cores and
this should not be related to concurrency, so 1 client seems fine. The
average of 10 runs, 15 seconds each look like this:

Thanks for running these tests. The reason I added so much
concurrency was that this AMD machine has some weird behaviour in
regards to power management. Every bios update I seem to get changes
the power management but it still is very unstable despite me having
it on the most optimal settings in the bios. Working it a bit harder
seems to help make it realise that there might be some urgency.

simple prepared 10/s 10/p 100/s 100/p
-------------------------------------------------------------
master 21847 59476 23343 59380 11757 56488
v4 21546 57757 22864 57704 11572 57350
v4+v5 23374 56089 24410 56140 14765 55302

The first two columns are your bench.sql, with -M simple or prepared.
The other columns are 10 or 100 values, /s is simple, /p is prepared.

Compared to master:

simple prepared 10/s 10/p 100/s 100/p
-------------------------------------------------------------
v4 98.62% 97.11% 97.95% 97.18% 98.43% 101.52%
v4+v5 106.99% 94.31% 104.57% 94.54% 125.59% 97.90%

That seems to mostly match your observation - there's a small
performance hit (~2%), although that might be due to changes in the
layout of the binary. And v4+v5 improves that a bit (even compared to
master), although I don't see the same 20% speedup.

I've spent more time hacking at this patch. I had a bit of a change
of heart earlier about having this new HashedScalarArrayOpExpr node
type. There were more places that I imagined that I needed to add
handling for it. For example, partprune.c needed to know about it to
allow partition pruning on them. While supporting that is just a few
lines to make a recursive call passing in the underlying
ScalarArrayOpExpr, I just didn't like the idea.

Instead, I think it'll be better just to add a new field to
ScalarArrayOpExpr and have the planner set that to tell the executor
that it should use a hash table to perform the lookups rather than a
linear search. This can just be the hash function oid, which also
saves the executor from having to look that up.

After quite a bit of hacking, I've ended up with the attached. I
added the required JIT code to teach the jit code about
EEOP_HASHED_SCALARARRAYOP.

I also wrote the missing regression test for non-strict equality ops
and moved the declaration for the simplehash.h code into
execExprInterp.c and forward declared ScalarArrayOpExprHashTable in
exprExpr.h. I also rewrote a large number of comments and fixed a few
things like missing permission checks for the hash function.

I've not done any further performance tests yet but will start those now.

David

Attachments:

saop_hash_v6.patchapplication/octet-stream; name=saop_hash_v6.patchDownload
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 23c0fb9379..b9e085ea4f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1149,6 +1149,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
 				AclResult	aclresult;
+				FmgrInfo   *hash_finfo;
+				FunctionCallInfo hash_fcinfo;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
@@ -1163,6 +1165,17 @@ ExecInitExprRec(Expr *node, ExprState *state,
 								   get_func_name(opexpr->opfuncid));
 				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
+				if (OidIsValid(opexpr->hashfuncid))
+				{
+					aclresult = pg_proc_aclcheck(opexpr->hashfuncid,
+												 GetUserId(),
+												 ACL_EXECUTE);
+					if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, OBJECT_FUNCTION,
+									   get_func_name(opexpr->hashfuncid));
+					InvokeFunctionExecuteHook(opexpr->hashfuncid);
+				}
+
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
@@ -1171,26 +1184,74 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
 
-				/* Evaluate scalar directly into left function argument */
-				ExecInitExprRec(scalararg, state,
-								&fcinfo->args[0].value, &fcinfo->args[0].isnull);
-
 				/*
-				 * Evaluate array argument into our return value.  There's no
-				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
+				 * When hashfn is set we create a EEOP_HASHED_SCALARARRAYOP
+				 * step instead of a EEOP_SCALARARRAYOP.  This provides much
+				 * faster repeat lookups when the number of items in the array
+				 * is anything but very small.
 				 */
-				ExecInitExprRec(arrayarg, state, resv, resnull);
-
-				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
-				ExprEvalPushStep(state, &scratch);
+				if (OidIsValid(opexpr->hashfuncid))
+				{
+					hash_finfo = palloc0(sizeof(FmgrInfo));
+					hash_fcinfo = palloc0(SizeForFunctionCallInfo(1));
+					fmgr_info(opexpr->hashfuncid, hash_finfo);
+					fmgr_info_set_expr((Node *) node, hash_finfo);
+					InitFunctionCallInfoData(*hash_fcinfo, hash_finfo,
+											 1, opexpr->inputcollid, NULL,
+											 NULL);
+
+					scratch.d.hashedscalararrayop.hash_finfo = hash_finfo;
+					scratch.d.hashedscalararrayop.hash_fcinfo_data = hash_fcinfo;
+					scratch.d.hashedscalararrayop.hash_fn_addr = hash_finfo->fn_addr;
+
+					/* Evaluate scalar directly into left function argument */
+					ExecInitExprRec(scalararg, state,
+						&fcinfo->args[0].value, &fcinfo->args[0].isnull);
+
+					/*
+					 * Evaluate array argument into our return value.  There's no
+					 * danger in that, because the return value is guaranteed to
+					 * be overwritten by EEOP_HASHED_SCALARARRAYOP, and will not
+					 * be passed to any other expression.
+					 */
+					ExecInitExprRec(arrayarg, state, resv, resnull);
+
+					/* And perform the operation */
+					scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
+
+					scratch.d.hashedscalararrayop.hash_finfo = hash_finfo;
+					scratch.d.hashedscalararrayop.hash_fcinfo_data = hash_fcinfo;
+					scratch.d.hashedscalararrayop.hash_fn_addr = hash_finfo->fn_addr;
+
+					scratch.d.hashedscalararrayop.finfo = finfo;
+					scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
+					scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;
+					ExprEvalPushStep(state, &scratch);
+				}
+				else
+				{
+					/* Evaluate scalar directly into left function argument */
+					ExecInitExprRec(scalararg, state,
+									&fcinfo->args[0].value,
+									&fcinfo->args[0].isnull);
+
+					/*
+					 * Evaluate array argument into our return value.  There's
+					 * no danger in that, because the return value is guaranteed
+					 * to be overwritten by EEOP_SCALARARRAYOP, and will not be
+					 * passed to any other expression.
+					 */
+					ExecInitExprRec(arrayarg, state, resv, resnull);
+
+					/* And perform the operation */
+					scratch.opcode = EEOP_SCALARARRAYOP;
+					scratch.d.scalararrayop.element_type = InvalidOid;
+					scratch.d.scalararrayop.useOr = opexpr->useOr;
+					scratch.d.scalararrayop.finfo = finfo;
+					scratch.d.scalararrayop.fcinfo_data = fcinfo;
+					scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+					ExprEvalPushStep(state, &scratch);
+				}
 				break;
 			}
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 6308286d8c..4ce42c3614 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -178,6 +178,43 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate
 															  ExprContext *aggcontext,
 															  int setno);
 
+typedef struct ScalarArrayOpExprHashEntry
+{
+	Datum key;
+	uint32		status;			/* hash status */
+	uint32		hash;			/* hash value (cached) */
+} ScalarArrayOpExprHashEntry;
+
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntry
+#define SH_KEY_TYPE Datum
+#define SH_SCOPE static inline
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+static bool saop_hash_element_match(struct saophash_hash *tb, Datum key1,
+									Datum key2);
+static uint32 saop_element_hash(struct saophash_hash *tb, Datum key);
+
+typedef struct ScalarArrayOpExprHashTable
+{
+	saophash_hash *hashtab;	/* underlying hash table */
+	struct ExprEvalStep *op;
+} ScalarArrayOpExprHashTable;
+
+/* Define parameters for ScalarArrayOpExpr hash table code generation. */
+#define SH_PREFIX saophash
+#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntry
+#define SH_KEY_TYPE Datum
+#define SH_KEY key
+#define SH_HASH_KEY(tb, key) saop_element_hash(tb, key)
+#define SH_EQUAL(tb, a, b) saop_hash_element_match(tb, a, b)
+#define SH_SCOPE static inline
+#define SH_STORE_HASH
+#define SH_GET_HASH(tb, a) a->hash
+#define SH_DEFINE
+#include "lib/simplehash.h"
+
 /*
  * Prepare ExprState for interpreted execution.
  */
@@ -426,6 +463,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_DOMAIN_CHECK,
 		&&CASE_EEOP_CONVERT_ROWTYPE,
 		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_HASHED_SCALARARRAYOP,
 		&&CASE_EEOP_XMLEXPR,
 		&&CASE_EEOP_AGGREF,
 		&&CASE_EEOP_GROUPING_FUNC,
@@ -1436,6 +1474,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_HASHED_SCALARARRAYOP)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalHashedScalarArrayOp(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_DOMAIN_NOTNULL)
 		{
 			/* too complex for an inline implementation */
@@ -3345,6 +3391,214 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	*op->resnull = resultnull;
 }
 
+/*
+ * Hash function for scalar array hash op elements.
+ *
+ * We use the element type's default hash opclass, and the column collation
+ * if the type is collation-sensitive.
+ */
+static uint32
+saop_element_hash(struct saophash_hash *tb, Datum key)
+{
+	ScalarArrayOpExprHashTable *elements_tab = (ScalarArrayOpExprHashTable *) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.hashedscalararrayop.hash_fcinfo_data;
+	Datum hash;
+
+	fcinfo->args[0].value = key;
+	fcinfo->args[0].isnull = false;
+
+	hash = elements_tab->op->d.hashedscalararrayop.hash_fn_addr(fcinfo);
+
+	return DatumGetUInt32(hash);
+}
+
+/*
+ * Matching function for scalar array hash op elements, to be used in hashtable
+ * lookups.
+ */
+static bool
+saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2)
+{
+	Datum result;
+
+	ScalarArrayOpExprHashTable *elements_tab = (ScalarArrayOpExprHashTable *) tb->private_data;
+	FunctionCallInfo fcinfo = elements_tab->op->d.hashedscalararrayop.fcinfo_data;
+
+	fcinfo->args[0].value = key1;
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = key2;
+	fcinfo->args[1].isnull = false;
+
+	result = elements_tab->op->d.hashedscalararrayop.fn_addr(fcinfo);
+
+	return DatumGetBool(result);
+}
+
+/*
+ * Evaluate "scalar op ANY (const array)".
+ *
+ * Similar to ExecEvalScalarArrayOp, but optimized for faster repeat lookups
+ * by building a hashtable on the first lookup.  This hashtable will be reused
+ * by subsequent lookups.  Unlike ExecEvalScalarArrayOp, this version only
+ * supports OR semantics.
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->args[0].
+ *
+ * The operator always yields boolean.
+ */
+void
+ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ScalarArrayOpExprHashTable *elements_tab = op->d.hashedscalararrayop.elements_tab;
+	FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
+	bool		strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
+	Datum		scalar = fcinfo->args[0].value;
+	bool		scalar_isnull = fcinfo->args[0].isnull;
+	Datum		result;
+	bool		resultnull;
+	bool		hashfound;
+
+	/* We don't setup a hashed scalar array op if the array const is null. */
+	Assert(!*op->resnull);
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in executing the search.
+	 */
+	if (fcinfo->args[0].isnull && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Build the hash table on first evaluation */
+	if (elements_tab == NULL)
+	{
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+		int			nitems;
+		bool		has_nulls = false;
+		char	   *s;
+		bits8	   *bitmap;
+		int			bitmask;
+		MemoryContext oldcontext;
+		ArrayType  *arr;
+
+		arr = DatumGetArrayTypeP(*op->resvalue);
+		nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &typlen,
+							 &typbyval,
+							 &typalign);
+
+		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+		elements_tab = (ScalarArrayOpExprHashTable *)
+				palloc(sizeof(ScalarArrayOpExprHashTable));
+		op->d.hashedscalararrayop.elements_tab = elements_tab;
+		elements_tab->op = op;
+
+		/*
+		 * Create the hash table sizing it according to the number of elements
+		 * in the array.  This does assume that the array has no duplicates.
+		 * If the array happens to contain many duplicate values then it'll
+		 * just mean that we sized the table a bit on the large side.
+		 */
+		elements_tab->hashtab = saophash_create(CurrentMemoryContext, nitems,
+												elements_tab);
+
+		MemoryContextSwitchTo(oldcontext);
+
+		s = (char *) ARR_DATA_PTR(arr);
+		bitmap = ARR_NULLBITMAP(arr);
+		bitmask = 1;
+		for (int i = 0; i < nitems; i++)
+		{
+			Datum		element;
+
+			/* Get array element, checking for NULL. */
+			if (bitmap && (*bitmap & bitmask) == 0)
+			{
+				has_nulls = true;
+			}
+			else
+			{
+				element = fetch_att(s, typbyval, typlen);
+				s = att_addlength_pointer(s, typlen, s);
+				s = (char *) att_align_nominal(s, typalign);
+
+				saophash_insert(elements_tab->hashtab, element, &hashfound);
+			}
+
+			/* Advance bitmap pointer if any. */
+			if (bitmap)
+			{
+				bitmask <<= 1;
+				if (bitmask == 0x100)
+				{
+					bitmap++;
+					bitmask = 1;
+				}
+			}
+		}
+
+		/*
+		 * Remember if we had any nulls so that we know if we need to execute
+		 * non-strict functions with a null lhs value if no match is found.
+		 */
+		op->d.hashedscalararrayop.has_nulls = has_nulls;
+	}
+
+	/* Check the hash to see if we have a match. */
+	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
+
+	result = BoolGetDatum(hashfound);
+	resultnull = false;
+
+	/*
+	 * If we didn't find a match in the array, we still might need to handle
+	 * the possibility of null values.  We didn't put any NULLs into the
+	 * hashtable, but instead marked if we found any when building the table
+	 * in has_nulls.
+	 */
+	if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
+	{
+		if (strictfunc)
+		{
+
+			/*
+			 * We have nulls in the array so a non-null lhs and no match must
+			 * yield NULL.
+			 */
+			result = (Datum) 0;
+			resultnull = true;
+		}
+		else
+		{
+			/*
+			 * Execute function will null rhs just once.
+			 *
+			 * The hash lookup path will have scribbled on the lhs argument so
+			 * we need to set it up also (even though we entered this function
+			 * with it already set).
+			 */
+			fcinfo->args[0].value = scalar;
+			fcinfo->args[0].isnull = scalar_isnull;
+			fcinfo->args[1].value = (Datum) 0;
+			fcinfo->args[1].isnull = true;
+
+			result = op->d.hashedscalararrayop.fn_addr(fcinfo);
+			resultnull = fcinfo->isnull;
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
 /*
  * Evaluate a NOT NULL domain constraint.
  */
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 42bf4754c5..0f9cc790c7 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1836,6 +1836,12 @@ llvm_compile_expr(ExprState *state)
 				LLVMBuildBr(b, opblocks[opno + 1]);
 				break;
 
+			case EEOP_HASHED_SCALARARRAYOP:
+				build_EvalXFunc(b, mod, "ExecEvalHashedScalarArrayOp",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_XMLEXPR:
 				build_EvalXFunc(b, mod, "ExecEvalXmlExpr",
 								v_state, op);
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 8bc58b641c..2deb65c5b5 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -126,6 +126,7 @@ void	   *referenced_functions[] =
 	ExecEvalRowNull,
 	ExecEvalSQLValueFunction,
 	ExecEvalScalarArrayOp,
+	ExecEvalHashedScalarArrayOp,
 	ExecEvalSubPlan,
 	ExecEvalSysVar,
 	ExecEvalWholeRowVar,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index fcc5ebb206..632cc31a04 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1716,6 +1716,7 @@ _copyScalarArrayOpExpr(const ScalarArrayOpExpr *from)
 
 	COPY_SCALAR_FIELD(opno);
 	COPY_SCALAR_FIELD(opfuncid);
+	COPY_SCALAR_FIELD(hashfuncid);
 	COPY_SCALAR_FIELD(useOr);
 	COPY_SCALAR_FIELD(inputcollid);
 	COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 936365e09a..a410a29a17 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -408,6 +408,12 @@ _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 		b->opfuncid != 0)
 		return false;
 
+	/* As above, hashfuncid may differ too */
+	if (a->hashfuncid != b->hashfuncid &&
+		a->hashfuncid != 0 &&
+		b->hashfuncid != 0)
+		return false;
+
 	COMPARE_SCALAR_FIELD(useOr);
 	COMPARE_SCALAR_FIELD(inputcollid);
 	COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4a8dc2d86d..c723f6d635 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1309,6 +1309,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 
 	WRITE_OID_FIELD(opno);
 	WRITE_OID_FIELD(opfuncid);
+	WRITE_OID_FIELD(hashfuncid);
 	WRITE_BOOL_FIELD(useOr);
 	WRITE_OID_FIELD(inputcollid);
 	WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 9924727851..3746668f52 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -831,6 +831,7 @@ _readScalarArrayOpExpr(void)
 
 	READ_OID_FIELD(opno);
 	READ_OID_FIELD(opfuncid);
+	READ_OID_FIELD(hashfuncid);
 	READ_BOOL_FIELD(useOr);
 	READ_OID_FIELD(inputcollid);
 	READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 05686d0194..ad7f50f1ca 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4436,21 +4436,51 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	}
 	else if (IsA(node, ScalarArrayOpExpr))
 	{
-		/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
-		 */
 		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
 		Node	   *arraynode = (Node *) lsecond(saop->args);
 		QualCost	sacosts;
+		QualCost	hcosts;
+		int			estarraylen = estimate_array_length(arraynode);
 
 		set_sa_opfuncid(saop);
 		sacosts.startup = sacosts.per_tuple = 0;
 		add_function_cost(context->root, saop->opfuncid, NULL,
 						  &sacosts);
-		context->total.startup += sacosts.startup;
-		context->total.per_tuple += sacosts.per_tuple *
-			estimate_array_length(arraynode) * 0.5;
+
+		if (OidIsValid(saop->hashfuncid))
+		{
+			/* Handle costs for hashed ScalarArrayOpExpr */
+
+			hcosts.startup = hcosts.per_tuple = 0;
+
+			add_function_cost(context->root, saop->hashfuncid, NULL, &hcosts);
+			context->total.startup += sacosts.startup + hcosts.startup;
+
+			/* Estimate the cost of building the hashtable. */
+			context->total.startup += estarraylen * hcosts.per_tuple;
+
+			/*
+			 * XXX should we charge a little bit for sacosts.per_tuple when
+			 * building the table, or is it ok to assume there will be zero
+			 * hash collision?
+			 */
+
+			 /*
+			  * Charge for hashtable lookups.  Charge a single hash and a
+			  * single comparison.
+			  */
+			context->total.per_tuple += hcosts.per_tuple + sacosts.per_tuple;
+		}
+		else
+		{
+			/*
+			 * Estimate that the operator will be applied to about half of the
+			 * array elements before the answer is determined.
+			 */
+			context->total.startup += sacosts.startup;
+			context->total.per_tuple += sacosts.per_tuple *
+				estimate_array_length(arraynode) * 0.5;
+		}
 	}
 	else if (IsA(node, Aggref) ||
 			 IsA(node, WindowFunc))
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 898d7fcb0b..1868c4eff4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1110,6 +1110,16 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 #endif
 	}
 
+	/*
+	 * Check for ANY ScalarArrayOpExpr with Const arrays and set the
+	 * hashfuncid of any that might execute more quickly by using hash lookups
+	 * instead of a linear search.
+	 */
+	if (kind == EXPRKIND_QUAL || kind == EXPRKIND_TARGET)
+	{
+		convert_saop_to_hashed_saop(expr);
+	}
+
 	/* Expand SubLinks to SubPlans */
 	if (root->parse->hasSubLinks)
 		expr = SS_process_sublinks(root, expr, (kind == EXPRKIND_QUAL));
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 70c0fa07e6..a2c0baaead 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1674,9 +1674,11 @@ fix_expr_common(PlannerInfo *root, Node *node)
 	}
 	else if (IsA(node, ScalarArrayOpExpr))
 	{
-		set_sa_opfuncid((ScalarArrayOpExpr *) node);
-		record_plan_function_dependency(root,
-										((ScalarArrayOpExpr *) node)->opfuncid);
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *)node;
+		set_sa_opfuncid(saop);
+		record_plan_function_dependency(root, saop->opfuncid);
+		if (!OidIsValid(saop->hashfuncid))
+			record_plan_function_dependency(root, saop->hashfuncid);
 	}
 	else if (IsA(node, Const))
 	{
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 8d4dc9cd10..42c3e4dc04 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -127,6 +127,7 @@ negate_clause(Node *node)
 
 					newopexpr->opno = negator;
 					newopexpr->opfuncid = InvalidOid;
+					newopexpr->hashfuncid = InvalidOid;
 					newopexpr->useOr = !saopexpr->useOr;
 					newopexpr->inputcollid = saopexpr->inputcollid;
 					newopexpr->args = saopexpr->args;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9a6e3dab83..526997327c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -106,6 +106,7 @@ static bool contain_leaked_vars_walker(Node *node, void *context);
 static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static List *find_nonnullable_vars_walker(Node *node, bool top_level);
 static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
+static bool convert_saop_to_hashed_saop_walker(Node *node, void *context);
 static Node *eval_const_expressions_mutator(Node *node,
 											eval_const_expressions_context *context);
 static bool contain_non_const_walker(Node *node, void *context);
@@ -2101,6 +2102,69 @@ eval_const_expressions(PlannerInfo *root, Node *node)
 	return eval_const_expressions_mutator(node, &context);
 }
 
+#define MIN_ARRAY_SIZE_FOR_HASHED_SAOP 9
+/*--------------------
+ * convert_saop_to_hashed_saop
+ *
+ * Recursively search 'node' for ScalarArrayOpExprs and fill in the hash
+ * function for any ScalarArrayOpExpr that looks like it would be useful to
+ * evaluate using a hash table rather than a linear search.
+ *
+ * We'll use a hash table if all of the following conditions are met:
+ * 1. The 2nd argument of the array contain only Consts.
+ * 2. useOr is true.
+ * 3. There's valid hash function for both left and righthand operands and
+ *	  these hash functions are the same.
+ * 4. If the array contains enough elements for us to consider it to be
+ *	  worthwhile using a hash table rather than a linear search.
+ */
+void
+convert_saop_to_hashed_saop(Node *node)
+{
+	(void) convert_saop_to_hashed_saop_walker(node, NULL);
+}
+
+static bool
+convert_saop_to_hashed_saop_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Expr	   *arrayarg = (Expr *) lsecond(saop->args);
+		Oid			lefthashfunc;
+		Oid			righthashfunc;
+
+		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
+			!((Const *) arrayarg)->constisnull &&
+			get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
+			lefthashfunc == righthashfunc)
+		{
+			Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+			ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+			int			nitems;
+
+			/*
+			 * Only fill in the hash functions if the array looks large enough
+			 * for it to be worth hashing instead of doing a linear search.
+			 */
+			nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+			if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+			{
+				/* Looks good. Fill in the hash functions */
+				saop->hashfuncid = lefthashfunc;
+			}
+			return true;
+		}
+	}
+
+	return expression_tree_walker(node, convert_saop_to_hashed_saop_walker, NULL);
+}
+
+
 /*--------------------
  * estimate_expression_value
  *
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 24013bcac9..c379d72fce 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -894,6 +894,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
 	result = makeNode(ScalarArrayOpExpr);
 	result->opno = oprid(tup);
 	result->opfuncid = opform->oprcode;
+	result->hashfuncid = InvalidOid;
 	result->useOr = useOr;
 	/* inputcollid will be set by parse_collate.c */
 	result->args = args;
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 2b2b1dc1ad..e5d2be103f 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -3812,6 +3812,7 @@ make_partition_op_expr(PartitionKey key, int keynum,
 					saopexpr = makeNode(ScalarArrayOpExpr);
 					saopexpr->opno = operoid;
 					saopexpr->opfuncid = get_opcode(operoid);
+					saopexpr->hashfuncid = InvalidOid;
 					saopexpr->useOr = true;
 					saopexpr->inputcollid = key->partcollation[keynum];
 					saopexpr->args = list_make2(arg1, arrexpr);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 1b7f9865b0..35ad9979e0 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -20,6 +20,7 @@
 /* forward references to avoid circularity */
 struct ExprEvalStep;
 struct SubscriptingRefState;
+struct ScalarArrayOpExprHashTable;
 
 /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
 /* expression's interpreter has been initialized */
@@ -218,6 +219,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_CONVERT_ROWTYPE,
 	EEOP_SCALARARRAYOP,
+	EEOP_HASHED_SCALARARRAYOP,
 	EEOP_XMLEXPR,
 	EEOP_AGGREF,
 	EEOP_GROUPING_FUNC,
@@ -554,6 +556,21 @@ typedef struct ExprEvalStep
 			PGFunction	fn_addr;	/* actual call address */
 		}			scalararrayop;
 
+		/* for EEOP_HASHED_SCALARARRAYOP */
+		struct
+		{
+			bool		has_nulls;
+			struct ScalarArrayOpExprHashTable	   *elements_tab;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+			FmgrInfo   *hash_finfo;	/* function's lookup data */
+			FunctionCallInfo hash_fcinfo_data;	/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	hash_fn_addr;	/* actual call address */
+		}			hashedscalararrayop;
+
 		/* for EEOP_XMLEXPR */
 		struct
 		{
@@ -725,6 +742,8 @@ extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 								   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op,
+										ExprContext *econtext);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f2ac4e51f1..44b5381874 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -578,12 +578,19 @@ typedef OpExpr NullIfExpr;
  * is almost the same as for the underlying operator, but we need a useOr
  * flag to remember whether it's ANY or ALL, and we don't have to store
  * the result type (or the collation) because it must be boolean.
+ *
+ * ScalarArrayOpExpr with a valid hashfuncid is evaluated during execution by
+ * building a hash table containing the Const values from the rhs arg.  This
+ * table is probed during expression evaluation.  Only useOr=true
+ * ScalarArrayOpExpr with Const arrays on the rhs can have the hashfuncid
+ * field set. See convert_saop_to_hashed_saop().
  */
 typedef struct ScalarArrayOpExpr
 {
 	Expr		xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
-	Oid			opfuncid;		/* PG_PROC OID of underlying function */
+	Oid			opfuncid;		/* PG_PROC OID of comparison function */
+	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..68ebb84bf5 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -146,6 +146,8 @@ extern bool contain_volatile_functions_not_nextval(Node *clause);
 
 extern Node *eval_const_expressions(PlannerInfo *root, Node *node);
 
+extern void convert_saop_to_hashed_saop(Node *node);
+
 extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
 extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 05a6eb07b2..5944dfd5e1 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -158,3 +158,121 @@ select count(*) from date_tbl
     13
 (1 row)
 
+--
+-- Tests for ScalarArrayOpExpr with a hashfn
+--
+-- create a stable function so that the tests below are not
+-- evaluated using the planner's constant folding.
+begin;
+create function return_int_input(int) returns int as $$
+begin
+	return $1;
+end;
+$$ language plpgsql stable;
+create function return_text_input(text) returns text as $$
+begin
+	return $1;
+end;
+$$ language plpgsql stable;
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ t
+(1 row)
+
+rollback;
+-- Test with non-strict equality function.
+-- We need to create our own type for this.
+begin;
+create type myint;
+create function myintin(cstring) returns myint strict immutable language
+  internal as 'int4in';
+NOTICE:  return type myint is only a shell
+create function myintout(myint) returns cstring strict immutable language
+  internal as 'int4out';
+NOTICE:  argument type myint is only a shell
+create function myinthash(myint) returns integer strict immutable language
+  internal as 'hashint4';
+NOTICE:  argument type myint is only a shell
+create type myint (input = myintin, output = myintout, like = int4);
+create cast (int4 as myint) without function;
+create cast (myint as int4) without function;
+create function myinteq(myint, myint) returns bool as $$
+begin
+  if $1 is null and $2 is null then
+    return true;
+  else
+    return $1::int = $2::int;
+  end if;
+end;
+$$ language plpgsql immutable;
+create operator = (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = =,
+  negator    = <>,
+  procedure  = myinteq,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+create operator class myint_ops
+default for type myint using hash as
+  operator    1   =  (myint, myint),
+  function    1   myinthash(myint);
+create table inttest (a myint);
+insert into inttest values(1::myint),(null);
+-- try an array with enough elements to cause hashing
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+ 1
+ 
+(2 rows)
+
+-- ensure the result matched with the non-hashed version.  We simply remove
+-- some array elements so that we don't reach the hashing threshold.
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+ 1
+ 
+(2 rows)
+
+rollback;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 1ca8bb151c..b3fd1b5ecb 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -65,3 +65,88 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+--
+-- Tests for ScalarArrayOpExpr with a hashfn
+--
+
+-- create a stable function so that the tests below are not
+-- evaluated using the planner's constant folding.
+begin;
+
+create function return_int_input(int) returns int as $$
+begin
+	return $1;
+end;
+$$ language plpgsql stable;
+
+create function return_text_input(text) returns text as $$
+begin
+	return $1;
+end;
+$$ language plpgsql stable;
+
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_int_input(1) in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+
+rollback;
+
+-- Test with non-strict equality function.
+-- We need to create our own type for this.
+
+begin;
+
+create type myint;
+create function myintin(cstring) returns myint strict immutable language
+  internal as 'int4in';
+create function myintout(myint) returns cstring strict immutable language
+  internal as 'int4out';
+create function myinthash(myint) returns integer strict immutable language
+  internal as 'hashint4';
+
+create type myint (input = myintin, output = myintout, like = int4);
+
+create cast (int4 as myint) without function;
+create cast (myint as int4) without function;
+
+create function myinteq(myint, myint) returns bool as $$
+begin
+  if $1 is null and $2 is null then
+    return true;
+  else
+    return $1::int = $2::int;
+  end if;
+end;
+$$ language plpgsql immutable;
+
+create operator = (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = =,
+  negator    = <>,
+  procedure  = myinteq,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+
+create operator class myint_ops
+default for type myint using hash as
+  operator    1   =  (myint, myint),
+  function    1   myinthash(myint);
+
+create table inttest (a myint);
+insert into inttest values(1::myint),(null);
+
+-- try an array with enough elements to cause hashing
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+-- ensure the result matched with the non-hashed version.  We simply remove
+-- some array elements so that we don't reach the hashing threshold.
+select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+
+rollback;
#49David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#48)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, 8 Apr 2021 at 18:50, David Rowley <dgrowleyml@gmail.com> wrote:

I've not done any further performance tests yet but will start those now.

I ran a set of tests on this:

select * from a where a in( < 1 to 10 > );
and
select * from a where a in( < 1 to 100 > );

the table "a" is just an empty table with a single int column.

I ran "pgbench -T 15 -c 16 -t 16" ten times each and the resulting tps
is averaged over the 10 runs.

With 10 items in the IN clause:
master: 99887.9098314 tps
patched: 103235.7616416 tps (3.35% faster)

With 100 items:
master: 62442.4838792 tps
patched:62275.4955754 tps (0.27% slower)

These tests are just designed to test the overhead of the additional
planning and expression initialisation. Testing the actual
performance of the patch vs master with large IN lists shows the
expected significant speedups.

These results show that there's not much in the way of a measurable
slowdown in planning or executor startup from the additional code
which decides if we should hash the ScalarArrayOpExpr.

I think the changes in the patch are fairly isolated and the test
coverage is now pretty good. I'm planning on looking at the patch
again now and will consider pushing it for PG14.

David

#50David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#49)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, 8 Apr 2021 at 22:54, David Rowley <dgrowleyml@gmail.com> wrote:

I think the changes in the patch are fairly isolated and the test
coverage is now pretty good. I'm planning on looking at the patch
again now and will consider pushing it for PG14.

I push this with some minor cleanup from the v6 patch I posted earlier.

David

#51James Coleman
jtc331@gmail.com
In reply to: David Rowley (#50)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, Apr 8, 2021 at 8:01 AM David Rowley <dgrowleyml@gmail.com> wrote:

On Thu, 8 Apr 2021 at 22:54, David Rowley <dgrowleyml@gmail.com> wrote:

I think the changes in the patch are fairly isolated and the test
coverage is now pretty good. I'm planning on looking at the patch
again now and will consider pushing it for PG14.

I push this with some minor cleanup from the v6 patch I posted earlier.

David

Thank you!

I assume proper procedure for the CF entry is to move it into the
current CF and then mark it as committed, however I don't know how (or
don't have permissions?) to move it into the current CF. How does one
go about doing that?

Here's the entry: https://commitfest.postgresql.org/29/2542/

Thanks,
James

#52Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: James Coleman (#51)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 2021-Apr-08, James Coleman wrote:

I assume proper procedure for the CF entry is to move it into the
current CF and then mark it as committed, however I don't know how (or
don't have permissions?) to move it into the current CF. How does one
go about doing that?

Here's the entry: https://commitfest.postgresql.org/29/2542/

Done, thanks.

--
�lvaro Herrera 39�49'30"S 73�17'W

#53James Coleman
jtc331@gmail.com
In reply to: Alvaro Herrera (#52)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Thu, Apr 8, 2021 at 1:04 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

On 2021-Apr-08, James Coleman wrote:

I assume proper procedure for the CF entry is to move it into the
current CF and then mark it as committed, however I don't know how (or
don't have permissions?) to move it into the current CF. How does one
go about doing that?

Here's the entry: https://commitfest.postgresql.org/29/2542/

Done, thanks.

--
Álvaro Herrera 39°49'30"S 73°17'W

Thanks. Is that something I should be able to do myself (should I be
asking someone for getting privileges in the app to do so)? I'm not
sure what the project policy is on that.

James

#54Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: James Coleman (#53)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 2021-Apr-08, James Coleman wrote:

On Thu, Apr 8, 2021 at 1:04 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

On 2021-Apr-08, James Coleman wrote:

I assume proper procedure for the CF entry is to move it into the
current CF and then mark it as committed, however I don't know how (or
don't have permissions?) to move it into the current CF. How does one
go about doing that?

Here's the entry: https://commitfest.postgresql.org/29/2542/

Done, thanks.

Thanks. Is that something I should be able to do myself

No, sorry.

--
�lvaro Herrera Valdivia, Chile
"La vida es para el que se aventura"

#55Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: David Rowley (#50)
2 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 4/8/21 2:00 PM, David Rowley wrote:

On Thu, 8 Apr 2021 at 22:54, David Rowley <dgrowleyml@gmail.com> wrote:

I think the changes in the patch are fairly isolated and the test
coverage is now pretty good. I'm planning on looking at the patch
again now and will consider pushing it for PG14.

I push this with some minor cleanup from the v6 patch I posted earlier.

I ran the same set of benchmarks on the v6, which I think should be
mostly identical to what was committed. I extended the test a bit to
test table with 0, 1, 5 and 1000 rows, and also with int and text
values, to see how it works with more expensive comparators.

I built binaries with gcc 9.2.0 and clang 11.0.0, the full results are
attached. There's a bit of difference between gcc and clang, but the
general behavior is about the same, so I'll only present gcc results to
keep this simple. I'll only throughput comparison to master, so >1.0
means good, <1.0 means bad. If you're interested in actual tps, see the
full results.

For the v5 patch (actually v4-0001 + v5-0002) and v6, the results are:

integer column / v5
===================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 97% 97% 97% 107% 126% 108%
1 95% 82% 94% 108% 132% 110%
5 95% 83% 95% 108% 132% 110%
1000 129% 481% 171% 131% 382% 165%

integer column / v6
===================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 97% 97% 97% 98% 98% 98%
1 96% 84% 95% 97% 97% 98%
5 97% 85% 96% 98% 97% 97%
1000 129% 489% 172% 128% 330% 162%

text column / v5
================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 100% 100% 100% 106% 119% 108%
1 96% 81% 95% 107% 120% 109%
5 97% 82% 96% 107% 121% 109%
1000 291% 1622% 402% 255% 1092% 337%

text column / v6
================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 101% 101% 101% 98% 99% 99%
1 98% 82% 96% 98% 96% 97%
5 100% 84% 98% 98% 96% 98%
1000 297% 1645% 408% 255% 1000% 336%

Overall, the behavior for integer and text columns is the same, for both
patches. There's a couple interesting observations:

1) For the "simple" query mode, v5 helped quite a bit (20-30% speedup),
but v6 does not seem to help at all - it's either same or slower than
unpatched master.

I wonder why is that, and if we could get some of the speedup with v6?
At first I thought that maybe v5 is not building the hash table in cases
where v6 does, but that shouldn't be faster than master.

2) For the "prepared" mode, there's a clear performance hit the longer
the array is (for both v5 and v6). For 100 elements it's about 15%,
which is not great.

I think the reason is fairly simple - building the hash table is not
free, and with few rows it's not worth it - it'd be faster to just
search the array directly. Unfortunately, the logic that makes the
decision to switch to hashing only looks at the array length only, and
ignores the number of rows entirely. So I think if we want to address
this, convert_saop_to_hashed_saop needs to compare

has_build_cost + nrows * hash_lookup_cost

and

nrows * linear_lookup_cost

to make reasonable decision.

I was thinking that maybe we can ignore this, because people probably
have much larger tables in practice. But I'm not sure that's really
true, because there may be other quals and it's possible the preceding
ones are quite selective, filtering most of the rows.

I'm not sure how much of the necessary information we have available in
convert_saop_to_hashed_saop_walker, though :-( I suppose we know the
number of input rows for that plan node, not sure about selectivity of
the other quals, though.

It's also a bit strange that we get speedup for "simple" protocol, while
for "prepared" it gets slower. That seems counter-intuitive, because why
should we see opposite outcomes in those cases? I'd assume that we'll
see either speedup or slowdown in both cases, with the relative change
being more significant in the "prepared" mode.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

saop gcc.odsapplication/vnd.oasis.opendocument.spreadsheet; name="saop gcc.ods"Download
saop llvm.odsapplication/vnd.oasis.opendocument.spreadsheet; name="saop llvm.ods"Download
#56David Rowley
dgrowleyml@gmail.com
In reply to: Tomas Vondra (#55)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, 9 Apr 2021 at 09:32, Tomas Vondra <tomas.vondra@enterprisedb.com> wrote:

I ran the same set of benchmarks on the v6, which I think should be
mostly identical to what was committed. I extended the test a bit to
test table with 0, 1, 5 and 1000 rows, and also with int and text
values, to see how it works with more expensive comparators.

I built binaries with gcc 9.2.0 and clang 11.0.0, the full results are
attached. There's a bit of difference between gcc and clang, but the
general behavior is about the same, so I'll only present gcc results to
keep this simple. I'll only throughput comparison to master, so >1.0
means good, <1.0 means bad. If you're interested in actual tps, see the
full results.

For the v5 patch (actually v4-0001 + v5-0002) and v6, the results are:

integer column / v5
===================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 97% 97% 97% 107% 126% 108%
1 95% 82% 94% 108% 132% 110%
5 95% 83% 95% 108% 132% 110%
1000 129% 481% 171% 131% 382% 165%

I think we should likely ignore the v5 patch now. The reason is that
it was pretty much unfinished and there were many places that I'd not
yet added support for the HashedScalarArrayOpExpr node type yet. This
could cause these nodes to be skipped during node mutations or node
walking which would certainly make planning faster, just not in a way
that's correct.

integer column / v6
===================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 97% 97% 97% 98% 98% 98%
1 96% 84% 95% 97% 97% 98%
5 97% 85% 96% 98% 97% 97%
1000 129% 489% 172% 128% 330% 162%

This is a really informative set of results. I can only guess that the
slowdown of the 100/prepared query is down to building the hash table.
I think that because the 0 rows test does not show the slowdown and we
only build the table when evaluating for the first time. There's a
slightly larger hit on 1 row vs 5 rows, which makes sense since the
rewards of the hash lookup start paying off more with more rows.

Looking at your tps numbers, I think I can see why we get the drop in
performance with prepared statements but not simple statements. This
seems to just be down to the fact that the planning time dominates in
the simple statement case. For example, the "1 row" test for 100/s
for v6 is 10023.3 tps, whereas the 100/p result is 44093.8 tps. With
master, prepared gets 52400.0 tps. So we could say the hash table
build costs us 8306.2 tps, or 3.59 microseconds per execution, per:

postgres=# select 1000000 / 52400.0 - 1000000 / 44093.8;
?column?
---------------------
-3.5949559161508538
(1 row)

If we look at the tps for the simple query version of the same test.
Master did 10309.6 tps, v6 did 10023.3 tps. If we apply that 3.59
microsecond slowdown to master's tps, then we get pretty close to
within 1% of the v6 tps:

postgres=# select 1000000 / (1000000 / 10309.6 + 3.59);
?column?
-----------------------
9941.6451581291294165
(1 row)

text column / v6
================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 101% 101% 101% 98% 99% 99%
1 98% 82% 96% 98% 96% 97%
5 100% 84% 98% 98% 96% 98%
1000 297% 1645% 408% 255% 1000% 336%

Overall, the behavior for integer and text columns is the same, for both
patches. There's a couple interesting observations:

1) For the "simple" query mode, v5 helped quite a bit (20-30% speedup),
but v6 does not seem to help at all - it's either same or slower than
unpatched master.

I think that's related to the fact that I didn't finish adding
HashedScalarArrayOpExpr processing to all places that needed it.

I wonder why is that, and if we could get some of the speedup with v6?
At first I thought that maybe v5 is not building the hash table in cases
where v6 does, but that shouldn't be faster than master.

I don't think v5 and v6 really do anything much differently in the
executor. The only difference is really during ExecInitExprRec() when
we initialize the expression. With v5 we had a case
T_HashedScalarArrayOpExpr: to handle the new node type, but in v6 we
have if (OidIsValid(opexpr->hashfuncid)). Oh, wait. I did add the
missing permissions check on the hash function, so that will account
for something. As far as I can see, that's required.

2) For the "prepared" mode, there's a clear performance hit the longer
the array is (for both v5 and v6). For 100 elements it's about 15%,
which is not great.

I think the reason is fairly simple - building the hash table is not
free, and with few rows it's not worth it - it'd be faster to just
search the array directly. Unfortunately, the logic that makes the
decision to switch to hashing only looks at the array length only, and
ignores the number of rows entirely. So I think if we want to address
this, convert_saop_to_hashed_saop needs to compare

has_build_cost + nrows * hash_lookup_cost

and

nrows * linear_lookup_cost

to make reasonable decision.

I thought about that but I was really worried that the performance of
ScalarArrayOpExpr would just become too annoyingly unpredictable. You
know fairly well that we can often get massive row underestimations in
the planner. (I guess you worked on ext stats mainly because of that)
The problem I want to avoid is the ones where we get a big row
underestimation but don't really get a bad plan as a result. For
example a query like:

SELECT * FROM big_table WHERE col1 = ... AND col2 = ... AND col3 = ...
AND col4 IN( ... big list of values ...);

If col1, col2 and col3 are highly correlated but individually fairly
selective, then we could massively underestimate how many rows the IN
clause will see (assuming no rearranging was done here).

I'm not completely opposed to the idea of taking the estimated rows
into account during planning. It might just mean having to move the
convert_saop_to_hashed_saop() call somewhere else. I imagine that's
fairly trivial to do. I just have concerns about doing so.

I was thinking that maybe we can ignore this, because people probably
have much larger tables in practice. But I'm not sure that's really
true, because there may be other quals and it's possible the preceding
ones are quite selective, filtering most of the rows.

I'm not sure how much of the necessary information we have available in
convert_saop_to_hashed_saop_walker, though :-( I suppose we know the
number of input rows for that plan node, not sure about selectivity of
the other quals, though.

It's also a bit strange that we get speedup for "simple" protocol, while
for "prepared" it gets slower. That seems counter-intuitive, because why
should we see opposite outcomes in those cases? I'd assume that we'll
see either speedup or slowdown in both cases, with the relative change
being more significant in the "prepared" mode.

I hope my theory above about the planner time dominating the overall
time shows why that is.

Another way to look at these result is by taking your tps value and
calculating how long it takes to do N number of transactions then
totalling up the time it takes. If I do that to calculate how long it
took each test to perform 1000 transactions and sum each test grouping
by mode and version with rollup on mode, I get:

time values are in seconds:

pg-master 28.07
prepared 11.28
simple 16.79

pg-v6 15.86
prepared 5.23
simple 10.63

So, the overall result when applying the total time is 177%.

David

#57Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: David Rowley (#56)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 4/9/21 1:21 AM, David Rowley wrote:

On Fri, 9 Apr 2021 at 09:32, Tomas Vondra <tomas.vondra@enterprisedb.com> wrote:

I ran the same set of benchmarks on the v6, which I think should be
mostly identical to what was committed. I extended the test a bit to
test table with 0, 1, 5 and 1000 rows, and also with int and text
values, to see how it works with more expensive comparators.

I built binaries with gcc 9.2.0 and clang 11.0.0, the full results are
attached. There's a bit of difference between gcc and clang, but the
general behavior is about the same, so I'll only present gcc results to
keep this simple. I'll only throughput comparison to master, so >1.0
means good, <1.0 means bad. If you're interested in actual tps, see the
full results.

For the v5 patch (actually v4-0001 + v5-0002) and v6, the results are:

integer column / v5
===================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 97% 97% 97% 107% 126% 108%
1 95% 82% 94% 108% 132% 110%
5 95% 83% 95% 108% 132% 110%
1000 129% 481% 171% 131% 382% 165%

I think we should likely ignore the v5 patch now. The reason is that
it was pretty much unfinished and there were many places that I'd not
yet added support for the HashedScalarArrayOpExpr node type yet. This
could cause these nodes to be skipped during node mutations or node
walking which would certainly make planning faster, just not in a way
that's correct.

integer column / v6
===================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 97% 97% 97% 98% 98% 98%
1 96% 84% 95% 97% 97% 98%
5 97% 85% 96% 98% 97% 97%
1000 129% 489% 172% 128% 330% 162%

This is a really informative set of results. I can only guess that the
slowdown of the 100/prepared query is down to building the hash table.
I think that because the 0 rows test does not show the slowdown and we
only build the table when evaluating for the first time. There's a
slightly larger hit on 1 row vs 5 rows, which makes sense since the
rewards of the hash lookup start paying off more with more rows.

Agreed. I think that's essentially what I wrote too.

Looking at your tps numbers, I think I can see why we get the drop in
performance with prepared statements but not simple statements. This
seems to just be down to the fact that the planning time dominates in
the simple statement case. For example, the "1 row" test for 100/s
for v6 is 10023.3 tps, whereas the 100/p result is 44093.8 tps. With
master, prepared gets 52400.0 tps. So we could say the hash table
build costs us 8306.2 tps, or 3.59 microseconds per execution, per:

postgres=# select 1000000 / 52400.0 - 1000000 / 44093.8;
?column?
---------------------
-3.5949559161508538
(1 row)

If we look at the tps for the simple query version of the same test.
Master did 10309.6 tps, v6 did 10023.3 tps. If we apply that 3.59
microsecond slowdown to master's tps, then we get pretty close to
within 1% of the v6 tps:

postgres=# select 1000000 / (1000000 / 10309.6 + 3.59);
?column?
-----------------------
9941.6451581291294165
(1 row)

Right, that makes perfect sense.

text column / v6
================

rows 10/p 100/p 16/p 10/s 100/s 16/s
-----------------------------------------------------------
0 101% 101% 101% 98% 99% 99%
1 98% 82% 96% 98% 96% 97%
5 100% 84% 98% 98% 96% 98%
1000 297% 1645% 408% 255% 1000% 336%

Overall, the behavior for integer and text columns is the same, for botYep,h
patches. There's a couple interesting observations:

1) For the "simple" query mode, v5 helped quite a bit (20-30% speedup),
but v6 does not seem to help at all - it's either same or slower than
unpatched master.

I think that's related to the fact that I didn't finish adding
HashedScalarArrayOpExpr processing to all places that needed it.

Not sure I understand. Shouldn't that effectively default to the
unpatched behavior? How could that result in code that is *faster* than
master?

I wonder why is that, and if we could get some of the speedup with v6?
At first I thought that maybe v5 is not building the hash table in cases
where v6 does, but that shouldn't be faster than master.

I don't think v5 and v6 really do anything much differently in the
executor. The only difference is really during ExecInitExprRec() when
we initialize the expression. With v5 we had a case
T_HashedScalarArrayOpExpr: to handle the new node type, but in v6 we
have if (OidIsValid(opexpr->hashfuncid)). Oh, wait. I did add the
missing permissions check on the hash function, so that will account
for something. As far as I can see, that's required.

I think the check is required, but I don't think that should be so very
expensive - it's likely one of many other permission checks during.

But I think the puzzle is not so much about v5 vs v6, but more about v5
vs. master. I still don't understand how v5 managed to be faster than
master, but maybe I'm missing something.

2) For the "prepared" mode, there's a clear performance hit the longer
the array is (for both v5 and v6). For 100 elements it's about 15%,
which is not great.

I think the reason is fairly simple - building the hash table is not
free, and with few rows it's not worth it - it'd be faster to just
search the array directly. Unfortunately, the logic that makes the
decision to switch to hashing only looks at the array length only, and
ignores the number of rows entirely. So I think if we want to address
this, convert_saop_to_hashed_saop needs to compare

has_build_cost + nrows * hash_lookup_cost

and

nrows * linear_lookup_cost

to make reasonable decision.

I thought about that but I was really worried that the performance of
ScalarArrayOpExpr would just become too annoyingly unpredictable. You
know fairly well that we can often get massive row underestimations in
the planner. (I guess you worked on ext stats mainly because of that)
The problem I want to avoid is the ones where we get a big row
underestimation but don't really get a bad plan as a result. For
example a query like:

SELECT * FROM big_table WHERE col1 = ... AND col2 = ... AND col3 = ...
AND col4 IN( ... big list of values ...);

If col1, col2 and col3 are highly correlated but individually fairly
selective, then we could massively underestimate how many rows the IN
clause will see (assuming no rearranging was done here).

I'm not completely opposed to the idea of taking the estimated rows
into account during planning. It might just mean having to move the
convert_saop_to_hashed_saop() call somewhere else. I imagine that's
fairly trivial to do. I just have concerns about doing so.

Hmm, yeah. I understand your concerns, but the way it works now it kinda
penalizes correct estimates. Imagine you have workload with simple OLTP
queries, the queries always hit only a couple rows - but we make it run
15% slower because we're afraid there might be under-estimate.

It's sensible to make the planning resilient to under-estimates, but I'm
not sure just ignoring the cardinality estimate with the justification
it might be wrong is good strategy. In a way, all the other planning
decisions assume it's correct, so why should this be any different?
We're not using seqscan exclusively just because the selectivity might
be wrong, making index scan ineffective, for example.

Maybe the right solution is to rely on the estimates, but then also
enable the hashing if we significantly cross the threshold during
execution. So for example we might get estimate 10 rows, and calculate
that the hashing would start winning at 100 rows, so we start without
hashing. But then at execution if we get 200 rows, we build the hash
table and start using it.

Yes, there's a risk that there are only 200 rows, and the time spent
building the hash table is wasted. But it's much more likely that there
are many more rows.

I was thinking that maybe we can ignore this, because people probably
have much larger tables in practice. But I'm not sure that's really
true, because there may be other quals and it's possible the preceding
ones are quite selective, filtering most of the rows.

I'm not sure how much of the necessary information we have available in
convert_saop_to_hashed_saop_walker, though :-( I suppose we know the
number of input rows for that plan node, not sure about selectivity of
the other quals, though.

It's also a bit strange that we get speedup for "simple" protocol, while
for "prepared" it gets slower. That seems counter-intuitive, because why
should we see opposite outcomes in those cases? I'd assume that we'll
see either speedup or slowdown in both cases, with the relative change
being more significant in the "prepared" mode.

I hope my theory above about the planner time dominating the overall
time shows why that is.

I think it does explain the v6 behavior, where prepared gets slower
while simple is about the same (with low row counts).

But I'm not sure I understand the v5, where simple got faster than master.

Another way to look at these result is by taking your tps value and
calculating how long it takes to do N number of transactions then
totalling up the time it takes. If I do that to calculate how long it
took each test to perform 1000 transactions and sum each test grouping
by mode and version with rollup on mode, I get:

time values are in seconds:

pg-master 28.07
prepared 11.28
simple 16.79

pg-v6 15.86
prepared 5.23
simple 10.63

So, the overall result when applying the total time is 177%.

Not sure how you got those numbers, or how it explains the results.

E.g. on v5, the results for 100 int values / 1 row look like this:

100/p 100/s
master 52400 10310
v5 43446 13610

I understand why the prepared mode got slower. I don't understand how
the simple mode got faster.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#58David Rowley
dgrowleyml@gmail.com
In reply to: Tomas Vondra (#57)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 10 Apr 2021 at 10:32, Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

But I think the puzzle is not so much about v5 vs v6, but more about v5
vs. master. I still don't understand how v5 managed to be faster than
master, but maybe I'm missing something.

Well, v5 wrapped ScalarArrayOpExpr inside HashedScalarArrayOpExpr, but
didn't add cases for HashedScalarArrayOpExpr in all locations where it
should have. For example, fix_expr_common() has a case for
ScalarArrayOpExpr, but if we gave it a HashedScalarArrayOpExpr it
would have very little work to do.

I've not gone and proved that's the exact reason why the planner
became faster, but I really don't see any other reason.

Maybe the right solution is to rely on the estimates, but then also
enable the hashing if we significantly cross the threshold during
execution. So for example we might get estimate 10 rows, and calculate
that the hashing would start winning at 100 rows, so we start without
hashing. But then at execution if we get 200 rows, we build the hash
table and start using it.

To do that, we'd need to store the number of evaluations of the
function somewhere. I'm not really sure that would be a good idea as I
imagine we'd need to store that in ExprEvalStep. I imagine if that
was a good idea then we'd have done the same for JIT.

On 4/9/21 1:21 AM, David Rowley wrote:

time values are in seconds:

pg-master 28.07
prepared 11.28
simple 16.79

pg-v6 15.86
prepared 5.23
simple 10.63

So, the overall result when applying the total time is 177%.

Not sure how you got those numbers, or how it explains the results.

I got it by doing "1 / tps * 1000" to get the time it would take to
execute 1000 transactions of each of your tests. I then grouped by
patch, mode and took the sum of the calculated number. My point was
that overall the patch is significantly faster. I was trying to
highlight that the 0 and 1 row test take up very little time and the
overhead of building the hash table is only showing up because the
query executes so quickly.

FWIW, I think executing a large IN clause on a table that has 0 rows
is likely not that interesting a case to optimise for. That's not the
same as a query that just returns 0 rows due to filtering out hundreds
or thousands of rows during execution. The overhead of building the
hash table is not going to show up very easily in that sort of case.

I understand why the prepared mode got slower. I don't understand how
the simple mode got faster.

I very much imagine v5 was faster at planning due to the unfinished
nature of the patch. I'd not added support for HashedScalarArrayOpExpr
in all the places I should have. That would result in the planner
skipping lots of work that it needs to do. The way I got it to work
was to add it, then just add enough cases in the planner to handle
HashedScalarArrayOpExpr so I didn't get any errors. I stopped after
that just to show the idea. Lack of errors does not mean it was
correct. At least setrefs.c was not properly handling
HashedScalarArrayOpExpr.

I really think it would be best if we just ignore the performance of
v5. Looking at the performance of a patch that was incorrectly
skipping a bunch of required work does not seem that fair.

David

#59Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: David Rowley (#58)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On 4/11/21 12:03 AM, David Rowley wrote:

On Sat, 10 Apr 2021 at 10:32, Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

But I think the puzzle is not so much about v5 vs v6, but more about v5
vs. master. I still don't understand how v5 managed to be faster than
master, but maybe I'm missing something.

Well, v5 wrapped ScalarArrayOpExpr inside HashedScalarArrayOpExpr, but
didn't add cases for HashedScalarArrayOpExpr in all locations where it
should have. For example, fix_expr_common() has a case for
ScalarArrayOpExpr, but if we gave it a HashedScalarArrayOpExpr it
would have very little work to do.

I've not gone and proved that's the exact reason why the planner
became faster, but I really don't see any other reason.

Maybe the right solution is to rely on the estimates, but then also
enable the hashing if we significantly cross the threshold during
execution. So for example we might get estimate 10 rows, and calculate
that the hashing would start winning at 100 rows, so we start without
hashing. But then at execution if we get 200 rows, we build the hash
table and start using it.

To do that, we'd need to store the number of evaluations of the
function somewhere. I'm not really sure that would be a good idea as I
imagine we'd need to store that in ExprEvalStep. I imagine if that
was a good idea then we'd have done the same for JIT.

Sure, we'd need to track the number of lookups, but I'd imagine that's
fairly cheap and we can stop once we switch to hash mode.

I'm not sure "JIT does not do that" is really a proof it's a bad idea.
My guess is it wasn't considered back then, and the current heuristics
is the simplest possible. So maybe it's the other way and we should
consider to do the same thing for JIT?

FWIW if we look at what JIT does, it'd argue it supports the approach to
trust the estimates. Because if we under-estimate stuff, the cost won't
exceed the "jit_above_cost" threshold, and we won't use JIT.

On 4/9/21 1:21 AM, David Rowley wrote:

time values are in seconds:

pg-master 28.07
prepared 11.28
simple 16.79

pg-v6 15.86
prepared 5.23
simple 10.63

So, the overall result when applying the total time is 177%.

Not sure how you got those numbers, or how it explains the results.

I got it by doing "1 / tps * 1000" to get the time it would take to
execute 1000 transactions of each of your tests. I then grouped by
patch, mode and took the sum of the calculated number. My point was
that overall the patch is significantly faster. I was trying to
highlight that the 0 and 1 row test take up very little time and the
overhead of building the hash table is only showing up because the
query executes so quickly.

Ah, I see. TBH I don't think combining the results gives us a very
meaningful value - those cases were quite arbitrary, but summing them
together like this assumes the workload has about 25% of each. But if
your workload is exclusively 0/1/5 rows it's going to be hit.

FWIW, I think executing a large IN clause on a table that has 0 rows
is likely not that interesting a case to optimise for. That's not the
same as a query that just returns 0 rows due to filtering out hundreds
or thousands of rows during execution. The overhead of building the
hash table is not going to show up very easily in that sort of case.

Yeah, it's probably true that queries with long IN lists are probably
dealing with many input rows. And you're right we don't really care
about how many rows are ultimately produced by the query (or even the
step with the IN list) - if we spent a lot of time to filter the rows
before applying the IN list, the time to initialize the hash table is
probably just noise.

I wonder what's the relationship between the length of the IN list and
the minimum number of rows needed for the hash to start winning.

I understand why the prepared mode got slower. I don't understand how
the simple mode got faster.

I very much imagine v5 was faster at planning due to the unfinished
nature of the patch. I'd not added support for HashedScalarArrayOpExpr
in all the places I should have. That would result in the planner
skipping lots of work that it needs to do. The way I got it to work
was to add it, then just add enough cases in the planner to handle
HashedScalarArrayOpExpr so I didn't get any errors. I stopped after
that just to show the idea. Lack of errors does not mean it was
correct. At least setrefs.c was not properly handling
HashedScalarArrayOpExpr.

I really think it would be best if we just ignore the performance of
v5. Looking at the performance of a patch that was incorrectly
skipping a bunch of required work does not seem that fair.

Aha! I was assuming v5 was correct, but if that assumption is incorrect
then the whole "v5 speedup" is just an illusion, aAnd you're right we
should simply ignore that. Thanks for the explanation!

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#60David Rowley
dgrowleyml@gmail.com
In reply to: Tomas Vondra (#59)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sun, 11 Apr 2021 at 10:38, Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

I wonder what's the relationship between the length of the IN list and
the minimum number of rows needed for the hash to start winning.

I made the attached spreadsheet which demonstrates the crossover point
using the costs that I coded into cost_qual_eval_walker().

It basically shows, for large arrays, that there are fairly
significant benefits to hashing for just 2 lookups and not hashing
only just wins for 1 lookup. However, the cost model does not account
for allocating memory for the hash table, which is far from free.

You can adjust the number of items in the IN clause by changing the
value in cell B1. The values in B2 and B3 are what I saw the planner
set when I tested with both INT and TEXT types.

David

Attachments:

cost_comparison_hashed_vs_non-hashed_saops.odsapplication/vnd.oasis.opendocument.spreadsheet; name=cost_comparison_hashed_vs_non-hashed_saops.odsDownload
#61David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#50)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, 9 Apr 2021 at 00:00, David Rowley <dgrowleyml@gmail.com> wrote:

I push this with some minor cleanup from the v6 patch I posted earlier.

I realised when working on something unrelated last night that we can
also do hash lookups for NOT IN too.

We'd just need to check if the operator's negator operator is
hashable. No new fields would need to be added to ScalarArrayOpExpr.
We'd just set the hashfuncid to the correct value and then set the
opfuncid to the negator function. In the executor, we'd know to check
if the value is in the table or not in the table based on the useOr
value.

I'm not really sure whether lack of NOT IN support is going to be a
source of bug reports for PG14 or not. If it was, then it might be
worth doing something about that for PG14. Otherwise, we can just
leave it for future work for PG15 and beyond. I personally don't have
any strong feelings either way, but I'm leaning towards just writing a
patch and thinking of pushing it sometime after we branch for PG15.

I've included the RMT, just in case they want to voice an opinion on that.

David

#62Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#61)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

David Rowley <dgrowleyml@gmail.com> writes:

I realised when working on something unrelated last night that we can
also do hash lookups for NOT IN too.

... and still get the behavior right for nulls?

regards, tom lane

#63David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#62)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, 13 Apr 2021 at 11:42, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I realised when working on something unrelated last night that we can
also do hash lookups for NOT IN too.

... and still get the behavior right for nulls?

Yeah, it will. There are already some special cases for NULLs in the
IN version. Those would need to be adapted for NOT IN.

David

#64James Coleman
jtc331@gmail.com
In reply to: David Rowley (#63)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Mon, Apr 12, 2021 at 7:49 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Tue, 13 Apr 2021 at 11:42, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I realised when working on something unrelated last night that we can
also do hash lookups for NOT IN too.

... and still get the behavior right for nulls?

Yeah, it will. There are already some special cases for NULLs in the
IN version. Those would need to be adapted for NOT IN.

I hadn't thought about using the negator operator directly that way
when I initially wrote the patch.

But also I didn't think a whole lot about the NOT IN case at all --
and there's no mention of such that I see in this thread or the
precursor thread. It's pretty obvious that it wasn't part of my
immediate need, but obviously it'd be nice to have the consistency.

All that to say this: my vote would be to put it into PG15 also.

James

#65James Coleman
jtc331@gmail.com
In reply to: James Coleman (#64)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Mon, Apr 12, 2021 at 10:07 PM James Coleman <jtc331@gmail.com> wrote:

On Mon, Apr 12, 2021 at 7:49 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Tue, 13 Apr 2021 at 11:42, Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

I realised when working on something unrelated last night that we can
also do hash lookups for NOT IN too.

... and still get the behavior right for nulls?

Yeah, it will. There are already some special cases for NULLs in the
IN version. Those would need to be adapted for NOT IN.

I hadn't thought about using the negator operator directly that way
when I initially wrote the patch.

But also I didn't think a whole lot about the NOT IN case at all --
and there's no mention of such that I see in this thread or the
precursor thread. It's pretty obvious that it wasn't part of my
immediate need, but obviously it'd be nice to have the consistency.

All that to say this: my vote would be to put it into PG15 also.

...and here's a draft patch. I can take this to a new thread if you'd
prefer; the one here already got committed, on the other hand this is
pretty strongly linked to this discussion, so I figured it made sense
to post it here.

James

Attachments:

v1-0001-Add-HashedScalarArrayOp-support-for-NOT-IN.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Add-HashedScalarArrayOp-support-for-NOT-IN.patchDownload
From 08c37c5b228a0a7e9546a481a789eb1c384fcfc7 Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Tue, 13 Apr 2021 13:36:38 -0400
Subject: [PATCH v1] Add HashedScalarArrayOp support for NOT IN

---
 src/backend/optimizer/prep/prepqual.c     |  1 +
 src/backend/optimizer/util/clauses.c      |  4 +-
 src/backend/parser/parse_oper.c           |  1 +
 src/include/nodes/primnodes.h             |  1 +
 src/test/regress/expected/expressions.out | 90 +++++++++++++++++++++++
 src/test/regress/sql/expressions.sql      | 31 ++++++++
 6 files changed, 126 insertions(+), 2 deletions(-)

diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 42c3e4dc04..2c29993b13 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -129,6 +129,7 @@ negate_clause(Node *node)
 					newopexpr->opfuncid = InvalidOid;
 					newopexpr->hashfuncid = InvalidOid;
 					newopexpr->useOr = !saopexpr->useOr;
+					newopexpr->isNegator = true;
 					newopexpr->inputcollid = saopexpr->inputcollid;
 					newopexpr->args = saopexpr->args;
 					newopexpr->location = saopexpr->location;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 526997327c..99e688426e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2137,8 +2137,8 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
 		Oid			lefthashfunc;
 		Oid			righthashfunc;
 
-		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
-			!((Const *) arrayarg)->constisnull &&
+		if ((saop->useOr || (!saop->useOr && saop->isNegator)) && arrayarg &&
+			IsA(arrayarg, Const) && !((Const *) arrayarg)->constisnull &&
 			get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
 			lefthashfunc == righthashfunc)
 		{
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index c379d72fce..222f15719a 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -896,6 +896,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
 	result->opfuncid = opform->oprcode;
 	result->hashfuncid = InvalidOid;
 	result->useOr = useOr;
+	result->isNegator = strcmp("<>", NameStr(opform->oprname)) == 0;
 	/* inputcollid will be set by parse_collate.c */
 	result->args = args;
 	result->location = location;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..819856395e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -592,6 +592,7 @@ typedef struct ScalarArrayOpExpr
 	Oid			opfuncid;		/* PG_PROC OID of comparison function */
 	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
 	bool		useOr;			/* true for ANY, false for ALL */
+	bool		isNegator;		/* true if NOT has been applied to opno */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
 	int			location;		/* token location, or -1 if unknown */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5944dfd5e1..2e88f1ca19 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -216,6 +216,61 @@ select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '
  t
 (1 row)
 
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ f
+(1 row)
+
 rollback;
 -- Test with non-strict equality function.
 -- We need to create our own type for this.
@@ -242,6 +297,11 @@ begin
   end if;
 end;
 $$ language plpgsql immutable;
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -252,6 +312,16 @@ create operator = (
   join       = eqjoinsel,
   merges
 );
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -266,6 +336,16 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
@@ -275,4 +355,14 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
 rollback;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index b3fd1b5ecb..6f9404c3be 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -93,6 +93,16 @@ select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
 select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
 
 rollback;
 
@@ -124,6 +134,12 @@ begin
 end;
 $$ language plpgsql immutable;
 
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
+
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -135,6 +151,17 @@ create operator = (
   merges
 );
 
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -145,8 +172,12 @@ insert into inttest values(1::myint),(null);
 
 -- try an array with enough elements to cause hashing
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
 
 rollback;
-- 
2.17.1

#66David Rowley
dgrowleyml@gmail.com
In reply to: James Coleman (#65)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Wed, 14 Apr 2021 at 05:40, James Coleman <jtc331@gmail.com> wrote:

...and here's a draft patch. I can take this to a new thread if you'd
prefer; the one here already got committed, on the other hand this is
pretty strongly linked to this discussion, so I figured it made sense
to post it here.

I only glanced at this when you sent it and I was confused about how
it works. The patch didn't look like how I imagined it should and I
couldn't see how the executor part worked without any changes.

Anyway, I decided to clear up my confusion tonight and apply the patch
to figure all this out... unfortunately, I see why I was confused
now. It actually does not work at all :-(

You're still passing the <> operator to get_op_hash_functions(), which
of course is not hashable, so we just never do hashing for NOT IN.

All your tests pass just fine because the standard non-hashed code path is used.

My idea was that you'd not add any fields to ScalarArrayOpExpr and for
soaps with useOr == false, check if the negator of the operator is
hashable. If so set the opfuncid to the negator operator's function.

I'm a bit undecided if it's safe to set the opfuncid to the negator
function. If anything were to set that again based on the opno then
it would likely set it to the wrong thing. We can't go changing the
opno either because EXPLAIN would display the wrong thing.

Anyway, I've attached what I ended up with after spending a few hours
looking at this.

I pretty much used all your tests as is with the exception of removing
one that looked duplicated.

David

Attachments:

v2-0001-Speedup-NOT-IN-with-a-set-of-Consts.patchapplication/octet-stream; name=v2-0001-Speedup-NOT-IN-with-a-set-of-Consts.patchDownload
From ebc3c795ca42a66d9d0debabd50c97ed90a568b4 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Sat, 24 Apr 2021 21:21:33 +1200
Subject: [PATCH v2] Speedup NOT IN() with a set of Consts

Similar to 50e17ad28, which allowed hash tables to be used for IN clauses
with a set of constants. Here we add the same feature for NOT IN clauses.

Much of the code is shared with the IN implementation, we mostly just need
to check if the negator operator for a !useOr ScalarArrayOpExpr is
hashable.  Only some small changes are required in the executor to ensure
we pay attention to useOr so that we correctly negate the return value in
the correct place.
---
 src/backend/executor/execExpr.c           |  1 +
 src/backend/executor/execExprInterp.c     | 18 ++++-
 src/backend/optimizer/util/clauses.c      | 76 +++++++++++++++-----
 src/include/executor/execExpr.h           |  1 +
 src/test/regress/expected/expressions.out | 84 +++++++++++++++++++++++
 src/test/regress/sql/expressions.sql      | 30 ++++++++
 6 files changed, 192 insertions(+), 18 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 77c9d785d9..1a74609045 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1220,6 +1220,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					/* And perform the operation */
 					scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
+					scratch.d.hashedscalararrayop.useOr = opexpr->useOr;
 					scratch.d.hashedscalararrayop.finfo = finfo;
 					scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
 					scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 094e22d392..7a65680a01 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3481,6 +3481,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 {
 	ScalarArrayOpExprHashTable *elements_tab = op->d.hashedscalararrayop.elements_tab;
 	FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
+	bool		useOr = op->d.hashedscalararrayop.useOr;
 	bool		strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
 	Datum		scalar = fcinfo->args[0].value;
 	bool		scalar_isnull = fcinfo->args[0].isnull;
@@ -3584,7 +3585,12 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	/* Check the hash to see if we have a match. */
 	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
 
-	result = BoolGetDatum(hashfound);
+	/* useOr == true means an IN clause, useOr == false is NOT IN */
+	if (useOr)
+		result = BoolGetDatum(hashfound);
+	else
+		result = BoolGetDatum(!hashfound);
+
 	resultnull = false;
 
 	/*
@@ -3593,7 +3599,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	 * hashtable, but instead marked if we found any when building the table
 	 * in has_nulls.
 	 */
-	if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
+	if (!hashfound && op->d.hashedscalararrayop.has_nulls)
 	{
 		if (strictfunc)
 		{
@@ -3621,6 +3627,14 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 
 			result = op->d.hashedscalararrayop.fn_addr(fcinfo);
 			resultnull = fcinfo->isnull;
+
+			/*
+			 * When doing NOT IN the function call we did above is the negator
+			 * of the NOT IN function, so we must reverse the result of the
+			 * function.
+			 */
+			if (!useOr)
+				result = !result;
 		}
 	}
 
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d9ad4efc5e..4f5907a3b5 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2137,27 +2137,71 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
 		Oid			lefthashfunc;
 		Oid			righthashfunc;
 
-		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
-			!((Const *) arrayarg)->constisnull &&
-			get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
-			lefthashfunc == righthashfunc)
+		if (arrayarg && IsA(arrayarg, Const) &&
+			!((Const *) arrayarg)->constisnull)
 		{
-			Datum		arrdatum = ((Const *) arrayarg)->constvalue;
-			ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-			int			nitems;
+			if (saop->useOr)
+			{
+				if (get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
 
-			/*
-			 * Only fill in the hash functions if the array looks large enough
-			 * for it to be worth hashing instead of doing a linear search.
-			 */
-			nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					/*
+					 * Only fill in the hash functions if the array looks large enough
+					 * for it to be worth hashing instead of doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 
-			if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+					}
+					return true;
+				}
+			}
+			else /* !saop->useOr */
 			{
-				/* Looks good. Fill in the hash functions */
-				saop->hashfuncid = lefthashfunc;
+				Oid		negator = get_negator(saop->opno);
+
+				/*
+				 * Check if this is a NOT IN using an operator whose negator
+				 * is hashable.  If so we can still build a hash table and
+				 * just ensure the lookup items are not in the hash table.
+				 */
+				if (OidIsValid(negator) &&
+					get_op_hash_functions(negator, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
+
+					/*
+					 * Only fill in the hash functions if the array looks large enough
+					 * for it to be worth hashing instead of doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+
+						/*
+						 * XXX is it safe enough just to set the opfuncid to
+						 * the negator's function?  We need to leave the opno
+						 * in place so that EXPLAIN shows the correct
+						 * operator.
+						 */
+						saop->opfuncid = get_opcode(negator);
+					}
+					return true;
+				}
 			}
-			return true;
 		}
 	}
 
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 785600d04d..c68668a7a0 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -574,6 +574,7 @@ typedef struct ExprEvalStep
 		struct
 		{
 			bool		has_nulls;
+			bool		useOr;	/* use OR or AND semantics? */
 			struct ScalarArrayOpExprHashTable *elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5944dfd5e1..84159cb21f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -216,6 +216,55 @@ select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '
  t
 (1 row)
 
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ f
+(1 row)
+
 rollback;
 -- Test with non-strict equality function.
 -- We need to create our own type for this.
@@ -242,6 +291,11 @@ begin
   end if;
 end;
 $$ language plpgsql immutable;
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -252,6 +306,16 @@ create operator = (
   join       = eqjoinsel,
   merges
 );
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -266,6 +330,16 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
@@ -275,4 +349,14 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
 rollback;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index b3fd1b5ecb..bf30f41505 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -93,6 +93,15 @@ select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
 select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
 
 rollback;
 
@@ -124,6 +133,12 @@ begin
 end;
 $$ language plpgsql immutable;
 
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
+
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -135,6 +150,17 @@ create operator = (
   merges
 );
 
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -145,8 +171,12 @@ insert into inttest values(1::myint),(null);
 
 -- try an array with enough elements to cause hashing
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
 
 rollback;
-- 
2.21.0.windows.1

#67James Coleman
jtc331@gmail.com
In reply to: David Rowley (#66)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, Apr 24, 2021 at 6:25 AM David Rowley <dgrowleyml@gmail.com> wrote:

On Wed, 14 Apr 2021 at 05:40, James Coleman <jtc331@gmail.com> wrote:

...and here's a draft patch. I can take this to a new thread if you'd
prefer; the one here already got committed, on the other hand this is
pretty strongly linked to this discussion, so I figured it made sense
to post it here.

I only glanced at this when you sent it and I was confused about how
it works. The patch didn't look like how I imagined it should and I
couldn't see how the executor part worked without any changes.

Anyway, I decided to clear up my confusion tonight and apply the patch
to figure all this out... unfortunately, I see why I was confused
now. It actually does not work at all :-(

You're still passing the <> operator to get_op_hash_functions(), which
of course is not hashable, so we just never do hashing for NOT IN.

All your tests pass just fine because the standard non-hashed code path is used.

I was surprised when it "just worked" too; I should have stopped to
verify the path was being taken. Egg on my face for not doing so. :(

My idea was that you'd not add any fields to ScalarArrayOpExpr and for
soaps with useOr == false, check if the negator of the operator is
hashable. If so set the opfuncid to the negator operator's function.

I'm a bit undecided if it's safe to set the opfuncid to the negator
function. If anything were to set that again based on the opno then
it would likely set it to the wrong thing. We can't go changing the
opno either because EXPLAIN would display the wrong thing.

I don't personally see a reason why this is a problem. But I also
don't know that I have enough knowledge of the codebase to say that
definitively.

Anyway, I've attached what I ended up with after spending a few hours
looking at this.

Overall I like this approach.

One thing I think we could clean up:

+ bool            useOr;  /* use OR or AND semantics? */
...
+ /* useOr == true means an IN clause, useOr == false is NOT IN */

I'm wondering if the intersection of these two lines implies that
useOr isn't quite the right name here. Perhaps something like
"negated"?

On the other hand (to make the counterargument) useOr would keep it
consistent with the other ones.

The other thing I got to thinking about was = ALL. It doesn't get
turned into a hash op because the negator of = isn't hashable. I think
it's correct that that's the determining factor, because I can't
imagine what it would mean to hash <>. But...I wanted to confirm I
wasn't missing something. We don't have explicit tests for that case,
but I'm not sure it's necessary either.

I pretty much used all your tests as is with the exception of removing
one that looked duplicated.

Sounds good.

James

#68David Rowley
dgrowleyml@gmail.com
In reply to: James Coleman (#67)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 8 May 2021 at 09:15, James Coleman <jtc331@gmail.com> wrote:

On Sat, Apr 24, 2021 at 6:25 AM David Rowley <dgrowleyml@gmail.com> wrote:

I'm a bit undecided if it's safe to set the opfuncid to the negator
function. If anything were to set that again based on the opno then
it would likely set it to the wrong thing. We can't go changing the
opno either because EXPLAIN would display the wrong thing.

I don't personally see a reason why this is a problem. But I also
don't know that I have enough knowledge of the codebase to say that
definitively.

The reason for my concern is that if the opfuncid is set to
InvalidOid, set_sa_opfuncid() always sets the ScalarArrayOpExpr's
opfuncid to get_opcode(opexpr->opno) . I'm effectively setting the
opfuncid to get_opcode(get_negator(opexpr->opno)), if anything were to
reset the ScalarArrayOpExpr's opfuncid to InvalidOid, then
set_sa_opfuncid() would repopulate it with the wrong value.

Maybe the solution there is to teach set_sa_opfuncid() about our
hashing NOT IN trick and have it check if (!opexpr->useOr &&
OidIsValid(opexpr->hashfuncid)) and if that's true then do
opexpr->opfuncid = get_opcode(get_negator(opexpr->opno)). Then we
could just not bothing setting opfuncid in
convert_saop_to_hashed_saop_walker().

Anyway, I've attached what I ended up with after spending a few hours
looking at this.

Overall I like this approach.

One thing I think we could clean up:

+ bool            useOr;  /* use OR or AND semantics? */
...
+ /* useOr == true means an IN clause, useOr == false is NOT IN */

I'm wondering if the intersection of these two lines implies that
useOr isn't quite the right name here. Perhaps something like
"negated"?

I'm not sure I want to go changing that. The whole IN() / NOT IN()
behaviour regarding NULLs all seems pretty weird until you mentally
replace a IN (1,2,3) with a = 1 OR a = 2 OR a = 3. And for the a NOT
IN(1,2,3) case, a <> 1 AND a <> 2 AND a <> 3. People can make a bit
more sense of the weirdness of NULLs with NOT IN when they mentally
convert their expression like that. I think having that in code is
useful too. Any optimisations that are added must match those
semantics.

The other thing I got to thinking about was = ALL. It doesn't get
turned into a hash op because the negator of = isn't hashable. I think
it's correct that that's the determining factor, because I can't
imagine what it would mean to hash <>. But...I wanted to confirm I
wasn't missing something. We don't have explicit tests for that case,
but I'm not sure it's necessary either.

It's important to think of other cases, I just don't think there's any
need to do anything for that one. Remember that we have the
restriction of requiring a set of Consts, so for that case to be met,
someone would have to write something like: col =
ALL('{1,1,1,1,1,1,1,1}'::int[]); I think if anyone comes along
complaining that a query containing that is not as fast as they'd like
then we might tell them that they should just use: col = 1. A sanity
checkup might not go amiss either.

David

#69Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Rowley (#68)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

David Rowley <dgrowleyml@gmail.com> writes:

On Sat, 8 May 2021 at 09:15, James Coleman <jtc331@gmail.com> wrote:

On Sat, Apr 24, 2021 at 6:25 AM David Rowley <dgrowleyml@gmail.com> wrote:

I'm a bit undecided if it's safe to set the opfuncid to the negator
function. If anything were to set that again based on the opno then
it would likely set it to the wrong thing. We can't go changing the
opno either because EXPLAIN would display the wrong thing.

I don't personally see a reason why this is a problem. But I also
don't know that I have enough knowledge of the codebase to say that
definitively.

The reason for my concern is that if the opfuncid is set to
InvalidOid, set_sa_opfuncid() always sets the ScalarArrayOpExpr's
opfuncid to get_opcode(opexpr->opno).

I will personally veto any design that involves setting opfuncid to
something that doesn't match the opno. That's just horrid, and it
will break something somewhere, either immediately or down the road.

I don't immediately see why you can't add an "invert" boolean flag to
ScalarArrayOpExpr and let the executor machinery deal with this. That'd
have the advantage of not having to depend on there being a negator.

regards, tom lane

#70James Coleman
jtc331@gmail.com
In reply to: Tom Lane (#69)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, May 7, 2021 at 9:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

David Rowley <dgrowleyml@gmail.com> writes:

On Sat, 8 May 2021 at 09:15, James Coleman <jtc331@gmail.com> wrote:

On Sat, Apr 24, 2021 at 6:25 AM David Rowley <dgrowleyml@gmail.com> wrote:

I'm a bit undecided if it's safe to set the opfuncid to the negator
function. If anything were to set that again based on the opno then
it would likely set it to the wrong thing. We can't go changing the
opno either because EXPLAIN would display the wrong thing.

I don't personally see a reason why this is a problem. But I also
don't know that I have enough knowledge of the codebase to say that
definitively.

The reason for my concern is that if the opfuncid is set to
InvalidOid, set_sa_opfuncid() always sets the ScalarArrayOpExpr's
opfuncid to get_opcode(opexpr->opno).

I will personally veto any design that involves setting opfuncid to
something that doesn't match the opno. That's just horrid, and it
will break something somewhere, either immediately or down the road.

This is the "project design" style/policy I don't have. Thanks.

I don't immediately see why you can't add an "invert" boolean flag to
ScalarArrayOpExpr and let the executor machinery deal with this. That'd
have the advantage of not having to depend on there being a negator.

Don't we need to have a negator to be able to look up the proper has
function? At least somewhere in the process you'd have to convert from
looking up the <> op to looking up the = op and then setting the
"invert" flag.

James

#71James Coleman
jtc331@gmail.com
In reply to: David Rowley (#68)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, May 7, 2021 at 8:38 PM David Rowley <dgrowleyml@gmail.com> wrote:

It's important to think of other cases, I just don't think there's any
need to do anything for that one. Remember that we have the
restriction of requiring a set of Consts, so for that case to be met,
someone would have to write something like: col =
ALL('{1,1,1,1,1,1,1,1}'::int[]); I think if anyone comes along
complaining that a query containing that is not as fast as they'd like
then we might tell them that they should just use: col = 1. A sanity
checkup might not go amiss either.

I wasn't concerned with trying to optimize this case (I don't think we
can anyway, at least not without adding new work, like de-duplicating
the array first). Though I do hope that someday I'll/we'll get around
to getting the stable subexpressions caching patch finished, and then
this will be able to work for more than constant arrays.

I just wanted to confirm we'd thought through the cases we can't
handle to ensure we're not accidentally covering them.

James

#72David Rowley
dgrowleyml@gmail.com
In reply to: James Coleman (#70)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 8 May 2021 at 13:37, James Coleman <jtc331@gmail.com> wrote:

On Fri, May 7, 2021 at 9:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I don't immediately see why you can't add an "invert" boolean flag to
ScalarArrayOpExpr and let the executor machinery deal with this. That'd
have the advantage of not having to depend on there being a negator.

Don't we need to have a negator to be able to look up the proper has
function? At least somewhere in the process you'd have to convert from
looking up the <> op to looking up the = op and then setting the
"invert" flag.

Yeah, we *do* need to ensure there's a negator in the planner as we
need to use it during hash probes. It's no good checking the hash
bucket we landed on does match with the <> operator's function. We
won't find many matches that way!

I'm not opposed to adding some new field if that's what it takes. I'd
imagine the new field will be something like negfuncid which will be
InvalidOid unless the hash function is set and useOr == false

David

#73David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#72)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 8 May 2021 at 14:04, David Rowley <dgrowleyml@gmail.com> wrote:

I'm not opposed to adding some new field if that's what it takes. I'd
imagine the new field will be something like negfuncid which will be
InvalidOid unless the hash function is set and useOr == false

Just while this is still swapped into main memory, I've attached a
patch that adds a new field to ScalarArrayOpExpr rather than
repurposing the existing field.

David

Attachments:

v3-0001-Speedup-NOT-IN-with-a-set-of-Consts.patchapplication/octet-stream; name=v3-0001-Speedup-NOT-IN-with-a-set-of-Consts.patchDownload
From 93124cada458c0cd1fa1f77b3bbcf3fb4c58b491 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Sat, 24 Apr 2021 21:21:33 +1200
Subject: [PATCH v3] Speedup NOT IN() with a set of Consts

Similar to 50e17ad28, which allowed hash tables to be used for IN clauses
with a set of constants. Here we add the same feature for NOT IN clauses.

Much of the code is shared with the IN implementation, we mostly just need
to check if the negator operator for a !useOr ScalarArrayOpExpr is
hashable.  Only some small changes are required in the executor to ensure
we pay attention to useOr so that we correctly negate the return value in
the correct place.
---
 src/backend/executor/execExpr.c           | 21 ++++--
 src/backend/executor/execExprInterp.c     | 18 ++++-
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/nodes/equalfuncs.c            |  6 ++
 src/backend/nodes/outfuncs.c              |  1 +
 src/backend/nodes/readfuncs.c             |  1 +
 src/backend/optimizer/plan/setrefs.c      |  3 +
 src/backend/optimizer/prep/prepqual.c     |  1 +
 src/backend/optimizer/util/clauses.c      | 74 +++++++++++++++-----
 src/backend/parser/parse_oper.c           |  1 +
 src/backend/partitioning/partbounds.c     |  1 +
 src/include/executor/execExpr.h           |  1 +
 src/include/nodes/primnodes.h             | 18 +++--
 src/test/regress/expected/expressions.out | 84 +++++++++++++++++++++++
 src/test/regress/sql/expressions.sql      | 30 ++++++++
 15 files changed, 235 insertions(+), 26 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 77c9d785d9..f82ac58a32 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1151,19 +1151,31 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				AclResult	aclresult;
 				FmgrInfo   *hash_finfo;
 				FunctionCallInfo hash_fcinfo;
+				Oid			cmpfuncid;
+
+				/*
+				 * Select the correct comparison function.  When we do hashed
+				 * NOT IN clauses the opfuncid will be the inequality
+				 * comparison function and negfuncid will be set to equality.
+				 * We need to use the equality function for hash probes.
+				 */
+				if (OidIsValid(opexpr->negfuncid))
+					cmpfuncid = opexpr->negfuncid;
+				else
+					cmpfuncid = opexpr->opfuncid;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
 				arrayarg = (Expr *) lsecond(opexpr->args);
 
 				/* Check permission to call function */
-				aclresult = pg_proc_aclcheck(opexpr->opfuncid,
+				aclresult = pg_proc_aclcheck(cmpfuncid,
 											 GetUserId(),
 											 ACL_EXECUTE);
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
-								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
+								   get_func_name(cmpfuncid));
+				InvokeFunctionExecuteHook(cmpfuncid);
 
 				if (OidIsValid(opexpr->hashfuncid))
 				{
@@ -1179,7 +1191,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				fmgr_info(cmpfuncid, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -1220,6 +1232,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					/* And perform the operation */
 					scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
+					scratch.d.hashedscalararrayop.useOr = opexpr->useOr;
 					scratch.d.hashedscalararrayop.finfo = finfo;
 					scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
 					scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 094e22d392..7a65680a01 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3481,6 +3481,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 {
 	ScalarArrayOpExprHashTable *elements_tab = op->d.hashedscalararrayop.elements_tab;
 	FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
+	bool		useOr = op->d.hashedscalararrayop.useOr;
 	bool		strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
 	Datum		scalar = fcinfo->args[0].value;
 	bool		scalar_isnull = fcinfo->args[0].isnull;
@@ -3584,7 +3585,12 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	/* Check the hash to see if we have a match. */
 	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
 
-	result = BoolGetDatum(hashfound);
+	/* useOr == true means an IN clause, useOr == false is NOT IN */
+	if (useOr)
+		result = BoolGetDatum(hashfound);
+	else
+		result = BoolGetDatum(!hashfound);
+
 	resultnull = false;
 
 	/*
@@ -3593,7 +3599,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	 * hashtable, but instead marked if we found any when building the table
 	 * in has_nulls.
 	 */
-	if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
+	if (!hashfound && op->d.hashedscalararrayop.has_nulls)
 	{
 		if (strictfunc)
 		{
@@ -3621,6 +3627,14 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 
 			result = op->d.hashedscalararrayop.fn_addr(fcinfo);
 			resultnull = fcinfo->isnull;
+
+			/*
+			 * When doing NOT IN the function call we did above is the negator
+			 * of the NOT IN function, so we must reverse the result of the
+			 * function.
+			 */
+			if (!useOr)
+				result = !result;
 		}
 	}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 632cc31a04..c5621eedf5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1717,6 +1717,7 @@ _copyScalarArrayOpExpr(const ScalarArrayOpExpr *from)
 	COPY_SCALAR_FIELD(opno);
 	COPY_SCALAR_FIELD(opfuncid);
 	COPY_SCALAR_FIELD(hashfuncid);
+	COPY_SCALAR_FIELD(negfuncid);
 	COPY_SCALAR_FIELD(useOr);
 	COPY_SCALAR_FIELD(inputcollid);
 	COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a410a29a17..5aae20e45f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -414,6 +414,12 @@ _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 		b->hashfuncid != 0)
 		return false;
 
+	/* Likewise for the negfuncid */
+	if (a->negfuncid != b->negfuncid &&
+		a->negfuncid != 0 &&
+		b->negfuncid != 0)
+		return false;
+
 	COMPARE_SCALAR_FIELD(useOr);
 	COMPARE_SCALAR_FIELD(inputcollid);
 	COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c723f6d635..de92cd94f3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1310,6 +1310,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_OID_FIELD(opno);
 	WRITE_OID_FIELD(opfuncid);
 	WRITE_OID_FIELD(hashfuncid);
+	WRITE_OID_FIELD(negfuncid);
 	WRITE_BOOL_FIELD(useOr);
 	WRITE_OID_FIELD(inputcollid);
 	WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3746668f52..9e6ba615d1 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -832,6 +832,7 @@ _readScalarArrayOpExpr(void)
 	READ_OID_FIELD(opno);
 	READ_OID_FIELD(opfuncid);
 	READ_OID_FIELD(hashfuncid);
+	READ_OID_FIELD(negfuncid);
 	READ_BOOL_FIELD(useOr);
 	READ_OID_FIELD(inputcollid);
 	READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9f40ed77e6..6261b02d76 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1681,6 +1681,9 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 		if (!OidIsValid(saop->hashfuncid))
 			record_plan_function_dependency(root, saop->hashfuncid);
+
+		if (!OidIsValid(saop->negfuncid))
+			record_plan_function_dependency(root, saop->hashfuncid);
 	}
 	else if (IsA(node, Const))
 	{
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 42c3e4dc04..8908a9652e 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -128,6 +128,7 @@ negate_clause(Node *node)
 					newopexpr->opno = negator;
 					newopexpr->opfuncid = InvalidOid;
 					newopexpr->hashfuncid = InvalidOid;
+					newopexpr->negfuncid = InvalidOid;
 					newopexpr->useOr = !saopexpr->useOr;
 					newopexpr->inputcollid = saopexpr->inputcollid;
 					newopexpr->args = saopexpr->args;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d9ad4efc5e..3e5400430c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2137,27 +2137,69 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
 		Oid			lefthashfunc;
 		Oid			righthashfunc;
 
-		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
-			!((Const *) arrayarg)->constisnull &&
-			get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
-			lefthashfunc == righthashfunc)
+		if (arrayarg && IsA(arrayarg, Const) &&
+			!((Const *) arrayarg)->constisnull)
 		{
-			Datum		arrdatum = ((Const *) arrayarg)->constvalue;
-			ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-			int			nitems;
+			if (saop->useOr)
+			{
+				if (get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
 
-			/*
-			 * Only fill in the hash functions if the array looks large enough
-			 * for it to be worth hashing instead of doing a linear search.
-			 */
-			nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					/*
+					 * Only fill in the hash functions if the array looks large enough
+					 * for it to be worth hashing instead of doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 
-			if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+					}
+					return true;
+				}
+			}
+			else /* !saop->useOr */
 			{
-				/* Looks good. Fill in the hash functions */
-				saop->hashfuncid = lefthashfunc;
+				Oid		negator = get_negator(saop->opno);
+
+				/*
+				 * Check if this is a NOT IN using an operator whose negator
+				 * is hashable.  If so we can still build a hash table and
+				 * just ensure the lookup items are not in the hash table.
+				 */
+				if (OidIsValid(negator) &&
+					get_op_hash_functions(negator, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
+
+					/*
+					 * Only fill in the hash functions if the array looks large enough
+					 * for it to be worth hashing instead of doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+
+						/*
+						 * Also set the negfuncid.  The executor will need
+						 * that to perform hashtable lookups.
+						 */
+						saop->negfuncid = get_opcode(negator);
+					}
+					return true;
+				}
 			}
-			return true;
 		}
 	}
 
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index c379d72fce..16dcd42471 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -895,6 +895,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
 	result->opno = oprid(tup);
 	result->opfuncid = opform->oprcode;
 	result->hashfuncid = InvalidOid;
+	result->negfuncid = InvalidOid;
 	result->useOr = useOr;
 	/* inputcollid will be set by parse_collate.c */
 	result->args = args;
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index c9c789297d..0b7d751d01 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -3813,6 +3813,7 @@ make_partition_op_expr(PartitionKey key, int keynum,
 					saopexpr->opno = operoid;
 					saopexpr->opfuncid = get_opcode(operoid);
 					saopexpr->hashfuncid = InvalidOid;
+					saopexpr->negfuncid = InvalidOid;
 					saopexpr->useOr = true;
 					saopexpr->inputcollid = key->partcollation[keynum];
 					saopexpr->args = list_make2(arg1, arrexpr);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 785600d04d..c68668a7a0 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -574,6 +574,7 @@ typedef struct ExprEvalStep
 		struct
 		{
 			bool		has_nulls;
+			bool		useOr;	/* use OR or AND semantics? */
 			struct ScalarArrayOpExprHashTable *elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..dc228b9284 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -580,10 +580,18 @@ typedef OpExpr NullIfExpr;
  * the result type (or the collation) because it must be boolean.
  *
  * A ScalarArrayOpExpr with a valid hashfuncid is evaluated during execution
- * by building a hash table containing the Const values from the rhs arg.
- * This table is probed during expression evaluation.  Only useOr=true
- * ScalarArrayOpExpr with Const arrays on the rhs can have the hashfuncid
- * field set. See convert_saop_to_hashed_saop().
+ * by building a hash table containing the Const values from the RHS arg.
+ * This table is probed during expression evaluation.  The planner will set
+ * hashfuncid to the hash function which must be used to build and probe the
+ * hash table.  The executor determines if it should use hash-based checks or
+ * the more traditional means based on if the hashfuncid is set or not.
+ *
+ * When performing hashed NOT IN, the negfuncid will also be set to the
+ * equality function which the hash table must use to build and probe the hash
+ * table.  opno and opfuncid will remain set to the <> operator and its
+ * corresponding function and won't be used during execution.  For
+ * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
+ * convert_saop_to_hashed_saop().
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -591,6 +599,8 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
 	Oid			opfuncid;		/* PG_PROC OID of comparison function */
 	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid
+								 * function or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5944dfd5e1..84159cb21f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -216,6 +216,55 @@ select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '
  t
 (1 row)
 
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ f
+(1 row)
+
 rollback;
 -- Test with non-strict equality function.
 -- We need to create our own type for this.
@@ -242,6 +291,11 @@ begin
   end if;
 end;
 $$ language plpgsql immutable;
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -252,6 +306,16 @@ create operator = (
   join       = eqjoinsel,
   merges
 );
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -266,6 +330,16 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
@@ -275,4 +349,14 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
 rollback;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index b3fd1b5ecb..bf30f41505 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -93,6 +93,15 @@ select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
 select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
 
 rollback;
 
@@ -124,6 +133,12 @@ begin
 end;
 $$ language plpgsql immutable;
 
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
+
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -135,6 +150,17 @@ create operator = (
   merges
 );
 
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -145,8 +171,12 @@ insert into inttest values(1::myint),(null);
 
 -- try an array with enough elements to cause hashing
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
 
 rollback;
-- 
2.21.0.windows.1

#74Zhihong Yu
zyu@yugabyte.com
In reply to: David Rowley (#73)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Fri, May 7, 2021 at 9:50 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, 8 May 2021 at 14:04, David Rowley <dgrowleyml@gmail.com> wrote:

I'm not opposed to adding some new field if that's what it takes. I'd
imagine the new field will be something like negfuncid which will be
InvalidOid unless the hash function is set and useOr == false

Just while this is still swapped into main memory, I've attached a
patch that adds a new field to ScalarArrayOpExpr rather than
repurposing the existing field.

David

Hi,

+       if (!OidIsValid(saop->negfuncid))
+           record_plan_function_dependency(root, saop->hashfuncid);

Is there a typo in the second line ? (root, saop->negfuncid)

Cheers

#75David Rowley
dgrowleyml@gmail.com
In reply to: Zhihong Yu (#74)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 8 May 2021 at 20:17, Zhihong Yu <zyu@yugabyte.com> wrote:

+       if (!OidIsValid(saop->negfuncid))
+           record_plan_function_dependency(root, saop->hashfuncid);

Is there a typo in the second line ? (root, saop->negfuncid)

Yeah, that's a mistake. Thanks for checking it.

David

#76David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#75)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Sat, 8 May 2021 at 20:29, David Rowley <dgrowleyml@gmail.com> wrote:

On Sat, 8 May 2021 at 20:17, Zhihong Yu <zyu@yugabyte.com> wrote:

+       if (!OidIsValid(saop->negfuncid))
+           record_plan_function_dependency(root, saop->hashfuncid);

Is there a typo in the second line ? (root, saop->negfuncid)

Yeah, that's a mistake. Thanks for checking it.

I've attached a patch which fixes the mistake mentioned above.

Also, dropped the RMT from the thread. I only introduced them when I
wanted some input about if hashing NOT IN should be included in PG14.
Nobody seems to think that should be done.

David

Attachments:

v4-0001-Speedup-NOT-IN-with-a-set-of-Consts.patchapplication/octet-stream; name=v4-0001-Speedup-NOT-IN-with-a-set-of-Consts.patchDownload
From 14010dcc378e139ae40125f83b7f7388770d1ccb Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Sat, 24 Apr 2021 21:21:33 +1200
Subject: [PATCH v4] Speedup NOT IN() with a set of Consts

Similar to 50e17ad28, which allowed hash tables to be used for IN clauses
with a set of constants. Here we add the same feature for NOT IN clauses.

Much of the code is shared with the IN implementation, we mostly just need
to check if the negator operator for a !useOr ScalarArrayOpExpr is
hashable.  Only some small changes are required in the executor to ensure
we pay attention to useOr so that we correctly negate the return value in
the correct place.
---
 src/backend/executor/execExpr.c           | 21 ++++--
 src/backend/executor/execExprInterp.c     | 18 ++++-
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/nodes/equalfuncs.c            |  6 ++
 src/backend/nodes/outfuncs.c              |  1 +
 src/backend/nodes/readfuncs.c             |  1 +
 src/backend/optimizer/plan/setrefs.c      |  3 +
 src/backend/optimizer/prep/prepqual.c     |  1 +
 src/backend/optimizer/util/clauses.c      | 76 +++++++++++++++-----
 src/backend/parser/parse_oper.c           |  1 +
 src/backend/partitioning/partbounds.c     |  1 +
 src/include/executor/execExpr.h           |  1 +
 src/include/nodes/primnodes.h             | 18 +++--
 src/test/regress/expected/expressions.out | 84 +++++++++++++++++++++++
 src/test/regress/sql/expressions.sql      | 30 ++++++++
 15 files changed, 237 insertions(+), 26 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 8c9f8a6aeb..542743fe99 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1205,19 +1205,31 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				AclResult	aclresult;
 				FmgrInfo   *hash_finfo;
 				FunctionCallInfo hash_fcinfo;
+				Oid			cmpfuncid;
+
+				/*
+				 * Select the correct comparison function.  When we do hashed
+				 * NOT IN clauses the opfuncid will be the inequality
+				 * comparison function and negfuncid will be set to equality.
+				 * We need to use the equality function for hash probes.
+				 */
+				if (OidIsValid(opexpr->negfuncid))
+					cmpfuncid = opexpr->negfuncid;
+				else
+					cmpfuncid = opexpr->opfuncid;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
 				arrayarg = (Expr *) lsecond(opexpr->args);
 
 				/* Check permission to call function */
-				aclresult = pg_proc_aclcheck(opexpr->opfuncid,
+				aclresult = pg_proc_aclcheck(cmpfuncid,
 											 GetUserId(),
 											 ACL_EXECUTE);
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
-								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
+								   get_func_name(cmpfuncid));
+				InvokeFunctionExecuteHook(cmpfuncid);
 
 				if (OidIsValid(opexpr->hashfuncid))
 				{
@@ -1233,7 +1245,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				fmgr_info(cmpfuncid, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -1274,6 +1286,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					/* And perform the operation */
 					scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
+					scratch.d.hashedscalararrayop.useOr = opexpr->useOr;
 					scratch.d.hashedscalararrayop.finfo = finfo;
 					scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
 					scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5483dee650..34f4078eba 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3493,6 +3493,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 {
 	ScalarArrayOpExprHashTable *elements_tab = op->d.hashedscalararrayop.elements_tab;
 	FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
+	bool		useOr = op->d.hashedscalararrayop.useOr;
 	bool		strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
 	Datum		scalar = fcinfo->args[0].value;
 	bool		scalar_isnull = fcinfo->args[0].isnull;
@@ -3596,7 +3597,12 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	/* Check the hash to see if we have a match. */
 	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
 
-	result = BoolGetDatum(hashfound);
+	/* useOr == true means an IN clause, useOr == false is NOT IN */
+	if (useOr)
+		result = BoolGetDatum(hashfound);
+	else
+		result = BoolGetDatum(!hashfound);
+
 	resultnull = false;
 
 	/*
@@ -3605,7 +3611,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	 * hashtable, but instead marked if we found any when building the table
 	 * in has_nulls.
 	 */
-	if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
+	if (!hashfound && op->d.hashedscalararrayop.has_nulls)
 	{
 		if (strictfunc)
 		{
@@ -3633,6 +3639,14 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 
 			result = op->d.hashedscalararrayop.fn_addr(fcinfo);
 			resultnull = fcinfo->isnull;
+
+			/*
+			 * When doing NOT IN the function call we did above is the negator
+			 * of the NOT IN function, so we must reverse the result of the
+			 * function.
+			 */
+			if (!useOr)
+				result = !result;
 		}
 	}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a89b0..002c2ea87f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1718,6 +1718,7 @@ _copyScalarArrayOpExpr(const ScalarArrayOpExpr *from)
 	COPY_SCALAR_FIELD(opno);
 	COPY_SCALAR_FIELD(opfuncid);
 	COPY_SCALAR_FIELD(hashfuncid);
+	COPY_SCALAR_FIELD(negfuncid);
 	COPY_SCALAR_FIELD(useOr);
 	COPY_SCALAR_FIELD(inputcollid);
 	COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d093dd..31f10b6237 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -414,6 +414,12 @@ _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 		b->hashfuncid != 0)
 		return false;
 
+	/* Likewise for the negfuncid */
+	if (a->negfuncid != b->negfuncid &&
+		a->negfuncid != 0 &&
+		b->negfuncid != 0)
+		return false;
+
 	COMPARE_SCALAR_FIELD(useOr);
 	COMPARE_SCALAR_FIELD(inputcollid);
 	COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14f0e..c670875857 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1311,6 +1311,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_OID_FIELD(opno);
 	WRITE_OID_FIELD(opfuncid);
 	WRITE_OID_FIELD(hashfuncid);
+	WRITE_OID_FIELD(negfuncid);
 	WRITE_BOOL_FIELD(useOr);
 	WRITE_OID_FIELD(inputcollid);
 	WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea07df..1054645906 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -832,6 +832,7 @@ _readScalarArrayOpExpr(void)
 	READ_OID_FIELD(opno);
 	READ_OID_FIELD(opfuncid);
 	READ_OID_FIELD(hashfuncid);
+	READ_OID_FIELD(negfuncid);
 	READ_BOOL_FIELD(useOr);
 	READ_OID_FIELD(inputcollid);
 	READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9f40ed77e6..e114bb1404 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1681,6 +1681,9 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 		if (!OidIsValid(saop->hashfuncid))
 			record_plan_function_dependency(root, saop->hashfuncid);
+
+		if (!OidIsValid(saop->negfuncid))
+			record_plan_function_dependency(root, saop->negfuncid);
 	}
 	else if (IsA(node, Const))
 	{
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 42c3e4dc04..8908a9652e 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -128,6 +128,7 @@ negate_clause(Node *node)
 					newopexpr->opno = negator;
 					newopexpr->opfuncid = InvalidOid;
 					newopexpr->hashfuncid = InvalidOid;
+					newopexpr->negfuncid = InvalidOid;
 					newopexpr->useOr = !saopexpr->useOr;
 					newopexpr->inputcollid = saopexpr->inputcollid;
 					newopexpr->args = saopexpr->args;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e117ab976e..5390a62e3f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2137,27 +2137,71 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
 		Oid			lefthashfunc;
 		Oid			righthashfunc;
 
-		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
-			!((Const *) arrayarg)->constisnull &&
-			get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
-			lefthashfunc == righthashfunc)
+		if (arrayarg && IsA(arrayarg, Const) &&
+			!((Const *) arrayarg)->constisnull)
 		{
-			Datum		arrdatum = ((Const *) arrayarg)->constvalue;
-			ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-			int			nitems;
+			if (saop->useOr)
+			{
+				if (get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
 
-			/*
-			 * Only fill in the hash functions if the array looks large enough
-			 * for it to be worth hashing instead of doing a linear search.
-			 */
-			nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					/*
+					 * Only fill in the hash functions if the array looks
+					 * large enough for it to be worth hashing instead of
+					 * doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 
-			if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+					}
+					return true;
+				}
+			}
+			else				/* !saop->useOr */
 			{
-				/* Looks good. Fill in the hash functions */
-				saop->hashfuncid = lefthashfunc;
+				Oid			negator = get_negator(saop->opno);
+
+				/*
+				 * Check if this is a NOT IN using an operator whose negator
+				 * is hashable.  If so we can still build a hash table and
+				 * just ensure the lookup items are not in the hash table.
+				 */
+				if (OidIsValid(negator) &&
+					get_op_hash_functions(negator, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
+
+					/*
+					 * Only fill in the hash functions if the array looks
+					 * large enough for it to be worth hashing instead of
+					 * doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+
+						/*
+						 * Also set the negfuncid.  The executor will need
+						 * that to perform hashtable lookups.
+						 */
+						saop->negfuncid = get_opcode(negator);
+					}
+					return true;
+				}
 			}
-			return true;
 		}
 	}
 
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index c379d72fce..16dcd42471 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -895,6 +895,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
 	result->opno = oprid(tup);
 	result->opfuncid = opform->oprcode;
 	result->hashfuncid = InvalidOid;
+	result->negfuncid = InvalidOid;
 	result->useOr = useOr;
 	/* inputcollid will be set by parse_collate.c */
 	result->args = args;
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 7925fcce3b..26cd896970 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -3816,6 +3816,7 @@ make_partition_op_expr(PartitionKey key, int keynum,
 					saopexpr->opno = operoid;
 					saopexpr->opfuncid = get_opcode(operoid);
 					saopexpr->hashfuncid = InvalidOid;
+					saopexpr->negfuncid = InvalidOid;
 					saopexpr->useOr = true;
 					saopexpr->inputcollid = key->partcollation[keynum];
 					saopexpr->args = list_make2(arg1, arrexpr);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 785600d04d..c68668a7a0 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -574,6 +574,7 @@ typedef struct ExprEvalStep
 		struct
 		{
 			bool		has_nulls;
+			bool		useOr;	/* use OR or AND semantics? */
 			struct ScalarArrayOpExprHashTable *elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..996c3e4016 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -580,10 +580,18 @@ typedef OpExpr NullIfExpr;
  * the result type (or the collation) because it must be boolean.
  *
  * A ScalarArrayOpExpr with a valid hashfuncid is evaluated during execution
- * by building a hash table containing the Const values from the rhs arg.
- * This table is probed during expression evaluation.  Only useOr=true
- * ScalarArrayOpExpr with Const arrays on the rhs can have the hashfuncid
- * field set. See convert_saop_to_hashed_saop().
+ * by building a hash table containing the Const values from the RHS arg.
+ * This table is probed during expression evaluation.  The planner will set
+ * hashfuncid to the hash function which must be used to build and probe the
+ * hash table.  The executor determines if it should use hash-based checks or
+ * the more traditional means based on if the hashfuncid is set or not.
+ *
+ * When performing hashed NOT IN, the negfuncid will also be set to the
+ * equality function which the hash table must use to build and probe the hash
+ * table.  opno and opfuncid will remain set to the <> operator and its
+ * corresponding function and won't be used during execution.  For
+ * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
+ * convert_saop_to_hashed_saop().
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -591,6 +599,8 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
 	Oid			opfuncid;		/* PG_PROC OID of comparison function */
 	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5944dfd5e1..84159cb21f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -216,6 +216,55 @@ select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '
  t
 (1 row)
 
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ f
+(1 row)
+
 rollback;
 -- Test with non-strict equality function.
 -- We need to create our own type for this.
@@ -242,6 +291,11 @@ begin
   end if;
 end;
 $$ language plpgsql immutable;
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -252,6 +306,16 @@ create operator = (
   join       = eqjoinsel,
   merges
 );
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -266,6 +330,16 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
@@ -275,4 +349,14 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
 rollback;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index b3fd1b5ecb..bf30f41505 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -93,6 +93,15 @@ select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
 select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
 
 rollback;
 
@@ -124,6 +133,12 @@ begin
 end;
 $$ language plpgsql immutable;
 
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
+
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -135,6 +150,17 @@ create operator = (
   merges
 );
 
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -145,8 +171,12 @@ insert into inttest values(1::myint),(null);
 
 -- try an array with enough elements to cause hashing
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
 
 rollback;
-- 
2.27.0

#77David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#76)
1 attachment(s)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

I've been looking at the NOT IN hashing patch again and after making a
few minor tweaks I think it's pretty much ready to go.

If anyone feels differently, please let me know in the next couple of
days. Otherwise, I plan on taking a final look and pushing it soon.

David

Attachments:

v5-0001-Use-hash-table-to-speed-up-NOT-IN-with-a-set-of-C.patchapplication/octet-stream; name=v5-0001-Use-hash-table-to-speed-up-NOT-IN-with-a-set-of-C.patchDownload
From deb6a300f774c2af255ab5129bb76f9744f8db64 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Sat, 24 Apr 2021 21:21:33 +1200
Subject: [PATCH v5] Use hash table to speed up NOT IN() with a set of Consts

Similar to 50e17ad28, which allowed hash tables to be used for IN clauses
with a set of constants, here we add the same feature for NOT IN clauses.

NOT IN evaluates the same as: WHERE a <> v1 AND a <> v2 AND a <> v3.
Obviously, if we're using a hash table we must be exactly equivalent to
that and return the same result taking into account that either side of
the condition could be NULL.

We support hash table lookups, not using the <> operator, but we check
if that operator has a negator that's hashable.  If so, then we're able
to use hash tables to evaluate the ScalarArrayOpExpr during execution.
Storing the negator operator requires adding a new field to
ScalarArrayOpExpr.
---
 src/backend/executor/execExpr.c           | 24 +++++--
 src/backend/executor/execExprInterp.c     | 17 ++++-
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/nodes/equalfuncs.c            |  6 ++
 src/backend/nodes/outfuncs.c              |  1 +
 src/backend/nodes/readfuncs.c             |  1 +
 src/backend/optimizer/plan/setrefs.c      |  3 +
 src/backend/optimizer/prep/prepqual.c     |  1 +
 src/backend/optimizer/util/clauses.c      | 76 +++++++++++++++-----
 src/backend/parser/parse_oper.c           |  1 +
 src/backend/partitioning/partbounds.c     |  1 +
 src/include/executor/execExpr.h           |  1 +
 src/include/nodes/primnodes.h             | 18 +++--
 src/test/regress/expected/expressions.out | 84 +++++++++++++++++++++++
 src/test/regress/sql/expressions.sql      | 30 ++++++++
 15 files changed, 239 insertions(+), 26 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 8c9f8a6aeb..a6394e50d1 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1205,19 +1205,34 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				AclResult	aclresult;
 				FmgrInfo   *hash_finfo;
 				FunctionCallInfo hash_fcinfo;
+				Oid			cmpfuncid;
+
+				/*
+				 * Select the correct comparison function.  When we do hashed
+				 * NOT IN clauses the opfuncid will be the inequality
+				 * comparison function and negfuncid will be set to equality.
+				 * We need to use the equality function for hash probes.
+				 */
+				if (OidIsValid(opexpr->negfuncid))
+				{
+					Assert(OidIsValid(opexpr->hashfuncid));
+					cmpfuncid = opexpr->negfuncid;
+				}
+				else
+					cmpfuncid = opexpr->opfuncid;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
 				arrayarg = (Expr *) lsecond(opexpr->args);
 
 				/* Check permission to call function */
-				aclresult = pg_proc_aclcheck(opexpr->opfuncid,
+				aclresult = pg_proc_aclcheck(cmpfuncid,
 											 GetUserId(),
 											 ACL_EXECUTE);
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
-								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
+								   get_func_name(cmpfuncid));
+				InvokeFunctionExecuteHook(cmpfuncid);
 
 				if (OidIsValid(opexpr->hashfuncid))
 				{
@@ -1233,7 +1248,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				/* Set up the primary fmgr lookup information */
 				finfo = palloc0(sizeof(FmgrInfo));
 				fcinfo = palloc0(SizeForFunctionCallInfo(2));
-				fmgr_info(opexpr->opfuncid, finfo);
+				fmgr_info(cmpfuncid, finfo);
 				fmgr_info_set_expr((Node *) node, finfo);
 				InitFunctionCallInfoData(*fcinfo, finfo, 2,
 										 opexpr->inputcollid, NULL, NULL);
@@ -1274,6 +1289,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 					/* And perform the operation */
 					scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
+					scratch.d.hashedscalararrayop.useOr = opexpr->useOr;
 					scratch.d.hashedscalararrayop.finfo = finfo;
 					scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
 					scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5483dee650..29ceff0729 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -3493,6 +3493,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 {
 	ScalarArrayOpExprHashTable *elements_tab = op->d.hashedscalararrayop.elements_tab;
 	FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
+	bool		inclause = op->d.hashedscalararrayop.useOr;
 	bool		strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
 	Datum		scalar = fcinfo->args[0].value;
 	bool		scalar_isnull = fcinfo->args[0].isnull;
@@ -3596,7 +3597,12 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	/* Check the hash to see if we have a match. */
 	hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
 
-	result = BoolGetDatum(hashfound);
+	/* the result depends on if the clause is an IN or NOT IN clause */
+	if (inclause)
+		result = BoolGetDatum(hashfound);	/* IN */
+	else
+		result = BoolGetDatum(!hashfound);	/* NOT IN */
+
 	resultnull = false;
 
 	/*
@@ -3605,7 +3611,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 	 * hashtable, but instead marked if we found any when building the table
 	 * in has_nulls.
 	 */
-	if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
+	if (!hashfound && op->d.hashedscalararrayop.has_nulls)
 	{
 		if (strictfunc)
 		{
@@ -3633,6 +3639,13 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
 
 			result = op->d.hashedscalararrayop.fn_addr(fcinfo);
 			resultnull = fcinfo->isnull;
+
+			/*
+			 * Reverse the result for NOT IN clauses since the above function
+			 * is the equality function and we need not-equals.
+			 */
+			if (!inclause)
+				result = !result;
 		}
 	}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bd87f23784..6fef067957 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1718,6 +1718,7 @@ _copyScalarArrayOpExpr(const ScalarArrayOpExpr *from)
 	COPY_SCALAR_FIELD(opno);
 	COPY_SCALAR_FIELD(opfuncid);
 	COPY_SCALAR_FIELD(hashfuncid);
+	COPY_SCALAR_FIELD(negfuncid);
 	COPY_SCALAR_FIELD(useOr);
 	COPY_SCALAR_FIELD(inputcollid);
 	COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index dba3e6b31e..b9cc7b199c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -414,6 +414,12 @@ _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 		b->hashfuncid != 0)
 		return false;
 
+	/* Likewise for the negfuncid */
+	if (a->negfuncid != b->negfuncid &&
+		a->negfuncid != 0 &&
+		b->negfuncid != 0)
+		return false;
+
 	COMPARE_SCALAR_FIELD(useOr);
 	COMPARE_SCALAR_FIELD(inputcollid);
 	COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e32b92e299..e09e4f77fe 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1311,6 +1311,7 @@ _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 	WRITE_OID_FIELD(opno);
 	WRITE_OID_FIELD(opfuncid);
 	WRITE_OID_FIELD(hashfuncid);
+	WRITE_OID_FIELD(negfuncid);
 	WRITE_BOOL_FIELD(useOr);
 	WRITE_OID_FIELD(inputcollid);
 	WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..3dec0a2508 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -832,6 +832,7 @@ _readScalarArrayOpExpr(void)
 	READ_OID_FIELD(opno);
 	READ_OID_FIELD(opfuncid);
 	READ_OID_FIELD(hashfuncid);
+	READ_OID_FIELD(negfuncid);
 	READ_BOOL_FIELD(useOr);
 	READ_OID_FIELD(inputcollid);
 	READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..210c4b3b14 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1687,6 +1687,9 @@ fix_expr_common(PlannerInfo *root, Node *node)
 
 		if (!OidIsValid(saop->hashfuncid))
 			record_plan_function_dependency(root, saop->hashfuncid);
+
+		if (!OidIsValid(saop->negfuncid))
+			record_plan_function_dependency(root, saop->negfuncid);
 	}
 	else if (IsA(node, Const))
 	{
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 42c3e4dc04..8908a9652e 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -128,6 +128,7 @@ negate_clause(Node *node)
 					newopexpr->opno = negator;
 					newopexpr->opfuncid = InvalidOid;
 					newopexpr->hashfuncid = InvalidOid;
+					newopexpr->negfuncid = InvalidOid;
 					newopexpr->useOr = !saopexpr->useOr;
 					newopexpr->inputcollid = saopexpr->inputcollid;
 					newopexpr->args = saopexpr->args;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 059fa70254..8506165d68 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2140,27 +2140,71 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
 		Oid			lefthashfunc;
 		Oid			righthashfunc;
 
-		if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
-			!((Const *) arrayarg)->constisnull &&
-			get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
-			lefthashfunc == righthashfunc)
+		if (arrayarg && IsA(arrayarg, Const) &&
+			!((Const *) arrayarg)->constisnull)
 		{
-			Datum		arrdatum = ((Const *) arrayarg)->constvalue;
-			ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
-			int			nitems;
+			if (saop->useOr)
+			{
+				if (get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
 
-			/*
-			 * Only fill in the hash functions if the array looks large enough
-			 * for it to be worth hashing instead of doing a linear search.
-			 */
-			nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+					/*
+					 * Only fill in the hash functions if the array looks
+					 * large enough for it to be worth hashing instead of
+					 * doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
 
-			if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+					}
+					return true;
+				}
+			}
+			else				/* !saop->useOr */
 			{
-				/* Looks good. Fill in the hash functions */
-				saop->hashfuncid = lefthashfunc;
+				Oid			negator = get_negator(saop->opno);
+
+				/*
+				 * Check if this is a NOT IN using an operator whose negator
+				 * is hashable.  If so we can still build a hash table and
+				 * just ensure the lookup items are not in the hash table.
+				 */
+				if (OidIsValid(negator) &&
+					get_op_hash_functions(negator, &lefthashfunc, &righthashfunc) &&
+					lefthashfunc == righthashfunc)
+				{
+					Datum		arrdatum = ((Const *) arrayarg)->constvalue;
+					ArrayType  *arr = (ArrayType *) DatumGetPointer(arrdatum);
+					int			nitems;
+
+					/*
+					 * Only fill in the hash functions if the array looks
+					 * large enough for it to be worth hashing instead of
+					 * doing a linear search.
+					 */
+					nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+					if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+					{
+						/* Looks good. Fill in the hash functions */
+						saop->hashfuncid = lefthashfunc;
+
+						/*
+						 * Also set the negfuncid.  The executor will need
+						 * that to perform hashtable lookups.
+						 */
+						saop->negfuncid = get_opcode(negator);
+					}
+					return true;
+				}
 			}
-			return true;
 		}
 	}
 
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 4e46079990..bc34a23afc 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -895,6 +895,7 @@ make_scalar_array_op(ParseState *pstate, List *opname,
 	result->opno = oprid(tup);
 	result->opfuncid = opform->oprcode;
 	result->hashfuncid = InvalidOid;
+	result->negfuncid = InvalidOid;
 	result->useOr = useOr;
 	/* inputcollid will be set by parse_collate.c */
 	result->args = args;
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 00c394445a..38baaefcda 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -3878,6 +3878,7 @@ make_partition_op_expr(PartitionKey key, int keynum,
 					saopexpr->opno = operoid;
 					saopexpr->opfuncid = get_opcode(operoid);
 					saopexpr->hashfuncid = InvalidOid;
+					saopexpr->negfuncid = InvalidOid;
 					saopexpr->useOr = true;
 					saopexpr->inputcollid = key->partcollation[keynum];
 					saopexpr->args = list_make2(arg1, arrexpr);
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 785600d04d..c68668a7a0 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -574,6 +574,7 @@ typedef struct ExprEvalStep
 		struct
 		{
 			bool		has_nulls;
+			bool		useOr;	/* use OR or AND semantics? */
 			struct ScalarArrayOpExprHashTable *elements_tab;
 			FmgrInfo   *finfo;	/* function's lookup data */
 			FunctionCallInfo fcinfo_data;	/* arguments etc */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..996c3e4016 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -580,10 +580,18 @@ typedef OpExpr NullIfExpr;
  * the result type (or the collation) because it must be boolean.
  *
  * A ScalarArrayOpExpr with a valid hashfuncid is evaluated during execution
- * by building a hash table containing the Const values from the rhs arg.
- * This table is probed during expression evaluation.  Only useOr=true
- * ScalarArrayOpExpr with Const arrays on the rhs can have the hashfuncid
- * field set. See convert_saop_to_hashed_saop().
+ * by building a hash table containing the Const values from the RHS arg.
+ * This table is probed during expression evaluation.  The planner will set
+ * hashfuncid to the hash function which must be used to build and probe the
+ * hash table.  The executor determines if it should use hash-based checks or
+ * the more traditional means based on if the hashfuncid is set or not.
+ *
+ * When performing hashed NOT IN, the negfuncid will also be set to the
+ * equality function which the hash table must use to build and probe the hash
+ * table.  opno and opfuncid will remain set to the <> operator and its
+ * corresponding function and won't be used during execution.  For
+ * non-hashtable based NOT INs, negfuncid will be set to InvalidOid.  See
+ * convert_saop_to_hashed_saop().
  */
 typedef struct ScalarArrayOpExpr
 {
@@ -591,6 +599,8 @@ typedef struct ScalarArrayOpExpr
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
 	Oid			opfuncid;		/* PG_PROC OID of comparison function */
 	Oid			hashfuncid;		/* PG_PROC OID of hash func or InvalidOid */
+	Oid			negfuncid;		/* PG_PROC OID of negator of opfuncid function
+								 * or InvalidOid.  See above */
 	bool		useOr;			/* true for ANY, false for ALL */
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* the scalar and array operands */
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 5944dfd5e1..84159cb21f 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -216,6 +216,55 @@ select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '
  t
 (1 row)
 
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+ ?column? 
+----------
+ f
+(1 row)
+
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+ ?column? 
+----------
+ 
+(1 row)
+
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ ?column? 
+----------
+ f
+(1 row)
+
 rollback;
 -- Test with non-strict equality function.
 -- We need to create our own type for this.
@@ -242,6 +291,11 @@ begin
   end if;
 end;
 $$ language plpgsql immutable;
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -252,6 +306,16 @@ create operator = (
   join       = eqjoinsel,
   merges
 );
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -266,6 +330,16 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+ a 
+---
+(0 rows)
+
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
@@ -275,4 +349,14 @@ select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,
  
 (2 rows)
 
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
+ a 
+---
+(0 rows)
+
 rollback;
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index b3fd1b5ecb..bf30f41505 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -93,6 +93,15 @@ select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
 select return_int_input(null::int) in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
 select return_text_input('a') in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- NOT IN
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 0);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 2, null);
+select return_int_input(1) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1, null);
+select return_int_input(1) not in (null, null, null, null, null, null, null, null, null, null, null);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
+select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
 
 rollback;
 
@@ -124,6 +133,12 @@ begin
 end;
 $$ language plpgsql immutable;
 
+create function myintne(myint, myint) returns bool as $$
+begin
+  return not myinteq($1, $2);
+end;
+$$ language plpgsql immutable;
+
 create operator = (
   leftarg    = myint,
   rightarg   = myint,
@@ -135,6 +150,17 @@ create operator = (
   merges
 );
 
+create operator <> (
+  leftarg    = myint,
+  rightarg   = myint,
+  commutator = <>,
+  negator    = =,
+  procedure  = myintne,
+  restrict   = eqsel,
+  join       = eqjoinsel,
+  merges
+);
+
 create operator class myint_ops
 default for type myint using hash as
   operator    1   =  (myint, myint),
@@ -145,8 +171,12 @@ insert into inttest values(1::myint),(null);
 
 -- try an array with enough elements to cause hashing
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null);
 -- ensure the result matched with the non-hashed version.  We simply remove
 -- some array elements so that we don't reach the hashing threshold.
 select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null);
+select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null);
 
 rollback;
-- 
2.30.2

#78David Rowley
dgrowleyml@gmail.com
In reply to: David Rowley (#77)
Re: Binary search in ScalarArrayOpExpr for OR'd constant arrays

On Tue, 6 Jul 2021 at 22:39, David Rowley <dgrowleyml@gmail.com> wrote:

If anyone feels differently, please let me know in the next couple of
days. Otherwise, I plan on taking a final look and pushing it soon.

After doing some very minor adjustments, I pushed this. (29f45e299).

Thanks to James and Zhihong for reviewing.

David