diff --git a/polkadot/core-primitives/src/lib.rs b/polkadot/core-primitives/src/lib.rs
index d72322cf3ff402e39ed6783a38b3b1d9b2e0b6cb..96d4c0039915c1701d41c4ead2dbd09aa8e197b8 100644
--- a/polkadot/core-primitives/src/lib.rs
+++ b/polkadot/core-primitives/src/lib.rs
@@ -20,11 +20,13 @@
 //!
 //! These core Polkadot types are used by the relay chain and the Parachains.
 
-use sp_runtime::{generic, MultiSignature, traits::{Verify, BlakeTwo256, IdentifyAccount}};
+use sp_runtime::{generic, MultiSignature, traits::{Verify, IdentifyAccount}};
 use parity_scale_codec::{Encode, Decode};
 #[cfg(feature = "std")]
 use parity_util_mem::MallocSizeOf;
 
+pub use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
+
 /// The block number type used by Polkadot.
 /// 32-bits will allow for 136 years of blocks assuming 1 block per second.
 pub type BlockNumber = u32;
diff --git a/polkadot/parachain/src/wasm_executor/mod.rs b/polkadot/parachain/src/wasm_executor/mod.rs
index 90c689f49050ea87a369d7c4c61aba3f1be6fef1..5192bf115de021b237385d346be025da8831397e 100644
--- a/polkadot/parachain/src/wasm_executor/mod.rs
+++ b/polkadot/parachain/src/wasm_executor/mod.rs
@@ -160,6 +160,25 @@ pub enum InternalError {
 	WasmWorker(String),
 }
 
+/// A cache of executors for different parachain Wasm instances.
+///
+/// This should be reused across candidate validation instances.
+pub struct ExecutorCache(sc_executor::WasmExecutor);
+
+impl Default for ExecutorCache {
+	fn default() -> Self {
+		ExecutorCache(sc_executor::WasmExecutor::new(
+			#[cfg(all(feature = "wasmtime", not(any(target_os = "android", target_os = "unknown"))))]
+			sc_executor::WasmExecutionMethod::Compiled,
+			#[cfg(any(not(feature = "wasmtime"), target_os = "android", target_os = "unknown"))]
+			sc_executor::WasmExecutionMethod::Interpreted,
+			// TODO: Make sure we don't use more than 1GB: https://github.com/paritytech/polkadot/issues/699
+			Some(1024),
+			HostFunctions::host_functions(),
+			8
+		))
+	}
+}
 
 /// Validate a candidate under the given validation code.
 ///
@@ -172,7 +191,12 @@ pub fn validate_candidate(
 ) -> Result<ValidationResult, ValidationError> {
 	match isolation_strategy {
 		IsolationStrategy::InProcess => {
-			validate_candidate_internal(validation_code, &params.encode(), spawner)
+			validate_candidate_internal(
+				&ExecutorCache::default(),
+				validation_code,
+				&params.encode(),
+				spawner,
+			)
 		},
 		#[cfg(not(any(target_os = "android", target_os = "unknown")))]
 		IsolationStrategy::ExternalProcessSelfHost(pool) => {
@@ -193,20 +217,12 @@ type HostFunctions = sp_io::SubstrateHostFunctions;
 ///
 /// This will fail if the validation code is not a proper parachain validation module.
 pub fn validate_candidate_internal(
+	executor: &ExecutorCache,
 	validation_code: &[u8],
 	encoded_call_data: &[u8],
 	spawner: impl SpawnNamed + 'static,
 ) -> Result<ValidationResult, ValidationError> {
-	let executor = sc_executor::WasmExecutor::new(
-		#[cfg(all(feature = "wasmtime", not(any(target_os = "android", target_os = "unknown"))))]
-		sc_executor::WasmExecutionMethod::Compiled,
-		#[cfg(any(not(feature = "wasmtime"), target_os = "android", target_os = "unknown"))]
-		sc_executor::WasmExecutionMethod::Interpreted,
-		// TODO: Make sure we don't use more than 1GB: https://github.com/paritytech/polkadot/issues/699
-		Some(1024),
-		HostFunctions::host_functions(),
-		8
-	);
+	let executor = &executor.0;
 
 	let mut extensions = Extensions::new();
 	extensions.register(sp_core::traits::TaskExecutorExt::new(spawner));
@@ -214,9 +230,16 @@ pub fn validate_candidate_internal(
 
 	let mut ext = ValidationExternalities(extensions);
 
+	// Expensive, but not more-so than recompiling the wasm module.
+	// And we need this hash to access the `sc_executor` cache.
+	let code_hash = {
+		use polkadot_core_primitives::{BlakeTwo256, HashT};
+		BlakeTwo256::hash(validation_code)
+	};
+
 	let res = executor.call_in_wasm(
 		validation_code,
-		None,
+		Some(code_hash.as_bytes().to_vec()),
 		"validate_block",
 		encoded_call_data,
 		&mut ext,
diff --git a/polkadot/parachain/src/wasm_executor/validation_host.rs b/polkadot/parachain/src/wasm_executor/validation_host.rs
index 79de6851994744cf757d0a20f22593ae048d4553..2db89b4bbaa131a9d057a51e78bab87d12907a90 100644
--- a/polkadot/parachain/src/wasm_executor/validation_host.rs
+++ b/polkadot/parachain/src/wasm_executor/validation_host.rs
@@ -151,6 +151,8 @@ pub fn run_worker(mem_id: &str) -> Result<(), String> {
 	memory.set(Event::WorkerReady as usize, EventState::Signaled)
 		.map_err(|e| format!("{} Error setting shared event: {:?}", process::id(), e))?;
 
+	let executor = super::ExecutorCache::default();
+
 	loop {
 		if watch_exit.load(atomic::Ordering::Relaxed) {
 			break;
@@ -184,7 +186,7 @@ pub fn run_worker(mem_id: &str) -> Result<(), String> {
 				let (call_data, _) = rest.split_at_mut(MAX_RUNTIME_MEM);
 				let (call_data, _) = call_data.split_at_mut(header.params_size as usize);
 
-				let result = validate_candidate_internal(code, call_data, task_executor.clone());
+				let result = validate_candidate_internal(&executor, code, call_data, task_executor.clone());
 				debug!(target: LOG_TARGET, "{} Candidate validated: {:?}", process::id(), result);
 
 				match result {