diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 606c920b06..2348eb3154 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -133,6 +133,12 @@ struct PartitionTupleRouting
  *		routing it through this table). A NULL value is stored if no tuple
  *		conversion is required.
  *
+ * lastPartInfo
+ * 		If non-NULL, ResultRelInfo for the partition that was most recently
+ * 		chosen as the routing target; ExecFindPartition() checks if the
+ * 		same one can be used for the current row before applying the tuple-
+ * 		routing algorithm to it.
+ *
  * indexes
  *		Array of partdesc->nparts elements.  For leaf partitions the index
  *		corresponds to the partition's ResultRelInfo in the encapsulating
@@ -150,6 +156,7 @@ typedef struct PartitionDispatchData
 	PartitionDesc partdesc;
 	TupleTableSlot *tupslot;
 	AttrMap    *tupmap;
+	ResultRelInfo *lastPartInfo;
 	int			indexes[FLEXIBLE_ARRAY_MEMBER];
 }			PartitionDispatchData;
 
@@ -291,6 +298,35 @@ ExecFindPartition(ModifyTableState *mtstate,
 
 		CHECK_FOR_INTERRUPTS();
 
+		/*
+		 * Check if the saved partition accepts this tuple by evaluating its
+		 * partition constraint against the tuple.  If it does, we save a trip
+		 * to get_partition_for_tuple(), which can be a slightly more expensive
+		 * way to get the same partition, especially if there are many
+		 * partitions to search through.
+		 */
+		if (dispatch->lastPartInfo)
+		{
+			TupleTableSlot *tmpslot;
+			TupleConversionMap *map;
+
+			rri = dispatch->lastPartInfo;
+			map = rri->ri_RootToPartitionMap;
+			if (map)
+				tmpslot = execute_attr_map_slot(map->attrMap, rootslot,
+												rri->ri_PartitionTupleSlot);
+			else
+				tmpslot = rootslot;
+			if (ExecPartitionCheck(rri, tmpslot, estate, false))
+			{
+				/* and restore ecxt's scantuple */
+				ecxt->ecxt_scantuple = ecxt_scantuple_saved;
+				MemoryContextSwitchTo(oldcxt);
+				return rri;
+			}
+			dispatch->lastPartInfo = rri = NULL;
+		}
+
 		rel = dispatch->reldesc;
 		partdesc = dispatch->partdesc;
 
@@ -372,6 +408,19 @@ ExecFindPartition(ModifyTableState *mtstate,
 			}
 			Assert(rri != NULL);
 
+			/*
+			 * Remember this partition for the next tuple inserted into this
+			 * parent; see at the top of this loop how it's decided whether
+			 * the next tuple can indeed reuse this partition.
+			 *
+			 * Do this only if we have range/list partitions, because only
+			 * in that case it's conceivable that consecutively inserted rows
+			 * tend to go into the same partition.
+			 */
+			if ((dispatch->key->strategy == PARTITION_STRATEGY_RANGE ||
+				 dispatch->key->strategy == PARTITION_STRATEGY_LIST))
+				dispatch->lastPartInfo = rri;
+
 			/* Signal to terminate the loop */
 			dispatch = NULL;
 		}
@@ -1051,6 +1100,8 @@ ExecInitPartitionDispatchInfo(EState *estate,
 		pd->tupslot = NULL;
 	}
 
+	pd->lastPartInfo = NULL;
+
 	/*
 	 * Initialize with -1 to signify that the corresponding partition's
 	 * ResultRelInfo or PartitionDispatch has not been created yet.
