diff --git a/docs/source/reference/envvars.rst b/docs/source/reference/envvars.rst
index 3041af0f2..45e7cdf70 100644
--- a/docs/source/reference/envvars.rst
+++ b/docs/source/reference/envvars.rst
@@ -235,6 +235,20 @@ Compilation options
         portable code (portable within the same architecture and OS),
         simply set ``NUMBA_CPU_NAME=generic``.
 
+.. envvar:: NUMBA_FUNCTION_CACHE_SIZE
+
+    Override the size of the function cache for retaining recently
+    deserialized functions in memory.  In systems like
+    `Dask <http://dask.pydata.org>`_, it is common for functions to be deserialized
+    multiple times.  Numba will cache functions as long as there is a
+    reference somewhere in the interpreter.  This cache size variable controls
+    how many functions that are no longer referenced will also be retained,
+    just in case they show up in the future.  The implementation of this is
+    not a true LRU, but the large size of the cache should be sufficient for
+    most situations.
+
+    *Default value:* 128
+
 
 GPU support
 -----------
diff --git a/numba/config.py b/numba/config.py
index defaaba98..b7f3a4e8d 100644
--- a/numba/config.py
+++ b/numba/config.py
@@ -152,6 +152,10 @@ class _EnvReloader(object):
         # Enable debugging of front-end operation (up to and including IR generation)
         DEBUG_FRONTEND = _readenv("NUMBA_DEBUG_FRONTEND", int, 0)
 
+        # How many recently deserialized functions to retain regardless
+        # of external references
+        FUNCTION_CACHE_SIZE = _readenv("NUMBA_FUNCTION_CACHE_SIZE", int, 128)
+
         # Enable logging of cache operation
         DEBUG_CACHE = _readenv("NUMBA_DEBUG_CACHE", int, DEBUG)
 
diff --git a/numba/dispatcher.py b/numba/dispatcher.py
index 471ea592b..58f52e4c4 100644
--- a/numba/dispatcher.py
+++ b/numba/dispatcher.py
@@ -514,6 +514,9 @@ class Dispatcher(_DispatcherBase):
         }
     # A {uuid -> instance} mapping, for deserialization
     _memo = weakref.WeakValueDictionary()
+    # hold refs to last N functions deserialized, retaining them in _memo
+    # regardless of whether there is another reference
+    _recent = collections.deque(maxlen=config.FUNCTION_CACHE_SIZE)
     __uuid = None
     __numba__ = 'py_func'
 
@@ -624,6 +627,7 @@ class Dispatcher(_DispatcherBase):
         assert self.__uuid is None
         self.__uuid = u
         self._memo[u] = self
+        self._recent.append(self)
 
     def compile(self, sig):
         if not self._can_compile:
diff --git a/numba/extending.py b/numba/extending.py
index 623dc8138..72451f20c 100644
--- a/numba/extending.py
+++ b/numba/extending.py
@@ -2,8 +2,9 @@
 import inspect
 import uuid
 import weakref
+import collections
 
-from numba import types
+from numba import types, config
 
 # Exported symbols
 from .typing.typeof import typeof_impl
@@ -216,6 +217,10 @@ class _Intrinsic(object):
     Dummy callable for intrinsic
     """
     _memo = weakref.WeakValueDictionary()
+    # hold refs to last N functions deserialized, retaining them in _memo
+    # regardless of whether there is another reference
+    _recent = collections.deque(maxlen=config.FUNCTION_CACHE_SIZE)
+
     __uuid = None
 
     def __init__(self, name, defn, support_literals=False):
@@ -241,6 +246,7 @@ class _Intrinsic(object):
         assert self.__uuid is None
         self.__uuid = u
         self._memo[u] = self
+        self._recent.append(self)
 
     def _register(self):
         from .typing.templates import make_intrinsic_template, infer_global

Slow performance on repeated deserialization
I'm running into performance problems when I deserialize a numba function many times within the same process.  I have a small example here:

### Process 1

```python
import numba
@numba.njit
def f(x):
    total = 0
    for i in range(len(x)):
        total += x[i]
    return total
import cloudpickle
cloudpickle.dumps(f)
```

```
b'\x80\x04\x95\xa8\x01\x00\x00\x00\x00\x00\x00\x8c\x0fnumba.serialize\x94\x8c\x12_rebuild_reduction\x94\x93\x94(\x8c\x16numba.targets.registry\x94\x8c\rCPUDispatcher\x94\x93\x94\x8c$64ebbc90-6b3a-11e8-963f-49110aa33909\x94(K\x04C\x043\r\r\n\x94C\xa4\xe3\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00C\x00\x00\x00s*\x00\x00\x00d\x01}\x01x t\x00t\x01|\x00\x83\x01\x83\x01D\x00]\x10}\x02|\x01|\x00|\x02\x19\x007\x00}\x01q\x12W\x00|\x01S\x00)\x02N\xe9\x00\x00\x00\x00)\x02\xda\x05range\xda\x03len)\x03\xda\x01x\xda\x05total\xda\x01i\xa9\x00r\x07\x00\x00\x00\xfa\x1e<ipython-input-2-359c566f9f92>\xda\x01f\x01\x00\x00\x00s\x08\x00\x00\x00\x00\x02\x04\x01\x12\x01\x10\x01\x94\x87\x94}\x94(\x8c\x05range\x94\x8c\x08builtins\x94\x8c\x05range\x94\x93\x94\x8c\x03len\x94\x8c\x08builtins\x94\x8c\x03len\x94\x93\x94\x8c\x08__name__\x94\x8c\x08__main__\x94u\x8c\x01f\x94Nt\x94}\x94}\x94\x8c\x08nopython\x94\x88s\x8c\x06direct\x94\x88]\x94t\x94R\x94.'
```

### Process 2

```python
import pickle
import numpy as np
x = np.random.random(1000)
b =  b'\x80\x04\x95\xa8\x01\x00\x00\x00\x00\x00\x00\x8c\x0fnumba.serialize\x94\x8c\x12_rebuild_reduction\x94\x93\x94(\x8c\x16numba.targets.registry\x94\x8c\rCPUDispatcher\x94\x93\x94\x8c$64ebbc90-6b3a-11e8-963f-49110aa33909\x94(K\x04C\x043\r\r\n\x94C\xa4\xe3\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00C\x00\x00\x00s*\x00\x00\x00d\x01}\x01x t\x00t\x01|\x00\x83\x01\x83\x01D\x00]\x10}\x02|\x01|\x00|\x02\x19\x007\x00}\x01q\x12W\x00|\x01S\x00)\x02N\xe9\x00\x00\x00\x00)\x02\xda\x05range\xda\x03len)\x03\xda\x01x\xda\x05total\xda\x01i\xa9\x00r\x07\x00\x00\x00\xfa\x1e<ipython-input-2-359c566f9f92>\xda\x01f\x01\x00\x00\x00s\x08\x00\x00\x00\x00\x02\x04\x01\x12\x01\x10\x01\x94\x87\x94}\x94(\x8c\x05range\x94\x8c\x08builtins\x94\x8c\x05range\x94\x93\x94\x8c\x03len\x94\x8c\x08builtins\x94\x8c\x03len\x94\x93\x94\x8c\x08__name__\x94\x8c\x08__main__\x94u\x8c\x01f\x94Nt\x94}\x94}\x94\x8c\x08nopython\x94\x88s\x8c\x06direct\x94\x88]\x94t\x94R\x94.'
```

We see that repeated calls of deserializing and then calling the function remain somewhat slow at 60ms per call.

```python
In [5]: %time pickle.loads(b)(x)
CPU times: user 255 ms, sys: 23.8 ms, total: 279 ms
Wall time: 281 ms
Out[5]: 515.6796588752262

In [6]: %time pickle.loads(b)(x)
CPU times: user 63.4 ms, sys: 3.84 ms, total: 67.2 ms
Wall time: 66.2 ms
Out[6]: 515.6796588752262

In [7]: %time pickle.loads(b)(x)
CPU times: user 68.3 ms, sys: 0 ns, total: 68.3 ms
Wall time: 67.1 ms
Out[7]: 515.6796588752262
```

The cost of deserializing is relatively low

```python
In [8]: %time f = pickle.loads(b)
CPU times: user 619 µs, sys: 66 µs, total: 685 µs
Wall time: 696 µs
```

And if we call this function many times then things are ok

```python
In [9]: %time f(x)
CPU times: user 65.9 ms, sys: 3.42 ms, total: 69.3 ms
Wall time: 68.6 ms
Out[9]: 515.6796588752262

In [10]: %time f(x)
CPU times: user 25 µs, sys: 2 µs, total: 27 µs
Wall time: 34.6 µs
Out[10]: 515.6796588752262

In [11]: %time f(x)
CPU times: user 36 µs, sys: 3 µs, total: 39 µs
Wall time: 52.7 µs
Out[11]: 515.6796588752262
```

So my guess is that we should be deduplicating things in some way.  Is this in scope for Numba to resolve or is this something that I should be handling on my end?

diff --git a/numba/tests/test_dispatcher.py b/numba/tests/test_dispatcher.py
index 38b8c862d..38d657a72 100644
--- a/numba/tests/test_dispatcher.py
+++ b/numba/tests/test_dispatcher.py
@@ -9,6 +9,8 @@ import sys
 import threading
 import warnings
 import inspect
+import pickle
+import weakref
 
 try:
     import jinja2
@@ -26,6 +28,7 @@ from .support import (TestCase, tag, temp_directory, import_dynamic,
                       override_env_config, capture_cache_log, captured_stdout)
 from numba.targets import codegen
 from numba.caching import _UserWideCacheLocator
+from numba.dispatcher import Dispatcher
 
 import llvmlite.binding as ll
 
@@ -337,6 +340,50 @@ class TestDispatcher(BaseTest):
         [cr] = bar.overloads.values()
         self.assertEqual(len(cr.lifted), 1)
 
+    def test_serialization(self):
+        """
+        Test serialization of Dispatcher objects
+        """
+        @jit(nopython=True)
+        def foo(x):
+            return x + 1
+
+        self.assertEqual(foo(1), 2)
+
+        # get serialization memo
+        memo = Dispatcher._memo
+        Dispatcher._recent.clear()
+        memo_size = len(memo)
+
+        # pickle foo and check memo size
+        serialized_foo = pickle.dumps(foo)
+        # increases the memo size
+        self.assertEqual(memo_size + 1, len(memo))
+
+        # unpickle
+        foo_rebuilt = pickle.loads(serialized_foo)
+        self.assertEqual(memo_size + 1, len(memo))
+
+        self.assertIs(foo, foo_rebuilt)
+
+        # do we get the same object even if we delete all the explict references?
+        id_orig = id(foo_rebuilt)
+        del foo
+        del foo_rebuilt
+        self.assertEqual(memo_size + 1, len(memo))
+        new_foo = pickle.loads(serialized_foo)
+        self.assertEqual(id_orig, id(new_foo))
+
+        # now clear the recent cache
+        ref = weakref.ref(new_foo)
+        del new_foo
+        Dispatcher._recent.clear()
+        self.assertEqual(memo_size, len(memo))
+
+        # show that deserializing creates a new object
+        newer_foo = pickle.loads(serialized_foo)
+        self.assertIs(ref(), None)
+
 
 class TestSignatureHandling(BaseTest):
     """
diff --git a/numba/tests/test_extending.py b/numba/tests/test_extending.py
index 3b377ce1b..565726ccb 100644
--- a/numba/tests/test_extending.py
+++ b/numba/tests/test_extending.py
@@ -676,7 +676,13 @@ class TestIntrinsic(TestCase):
         memo_size += 1
         self.assertEqual(memo_size, len(memo))
         del original  # remove original before unpickling
-        # by deleting, the memo entry is removed
+
+        # by deleting, the memo entry is NOT removed due to recent
+        # function queue
+        self.assertEqual(memo_size, len(memo))
+
+        # Manually force clear of _recent queue
+        _Intrinsic._recent.clear()
         memo_size -= 1
         self.assertEqual(memo_size, len(memo))
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc g++
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
exceptiongroup==1.2.2
importlib-metadata==6.7.0
iniconfig==2.0.0
llvmlite==0.39.1
-e git+https://github.com/numba/numba.git@5215237f67497c77d85642ee917b1553560bc194#egg=numba
numpy==1.21.6
packaging==24.0
pluggy==1.2.0
pytest==7.4.4
tomli==2.0.1
typing_extensions==4.7.1
zipp==3.15.0

name: numba
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - argparse==1.4.0
      - exceptiongroup==1.2.2
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - llvmlite==0.39.1
      - numpy==1.21.6
      - packaging==24.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/numba

numba/tests/test_extending.py::TestIntrinsic::test_deserialization
numba/tests/test_dispatcher.py::TestDispatcher::test_ambiguous_new_version numba/tests/test_dispatcher.py::TestDispatcher::test_coerce_input_types numba/tests/test_dispatcher.py::TestDispatcher::test_disabled_compilation numba/tests/test_dispatcher.py::TestDispatcher::test_disabled_compilation_nested_call numba/tests/test_dispatcher.py::TestDispatcher::test_disabled_compilation_through_list numba/tests/test_dispatcher.py::TestDispatcher::test_dyn_pyfunc numba/tests/test_dispatcher.py::TestDispatcher::test_explicit_signatures numba/tests/test_dispatcher.py::TestDispatcher::test_fingerprint_failure numba/tests/test_dispatcher.py::TestDispatcher::test_lock numba/tests/test_dispatcher.py::TestDispatcher::test_matching_error_message numba/tests/test_dispatcher.py::TestDispatcher::test_no_argument numba/tests/test_dispatcher.py::TestDispatcher::test_serialization numba/tests/test_dispatcher.py::TestDispatcher::test_signature_mismatch numba/tests/test_dispatcher.py::TestSignatureHandling::test_default_args numba/tests/test_dispatcher.py::TestSignatureHandling::test_named_args numba/tests/test_dispatcher.py::TestSignatureHandling::test_star_args numba/tests/test_dispatcher.py::TestSignatureHandlingObjectMode::test_default_args numba/tests/test_dispatcher.py::TestSignatureHandlingObjectMode::test_named_args numba/tests/test_dispatcher.py::TestSignatureHandlingObjectMode::test_star_args numba/tests/test_dispatcher.py::TestGeneratedDispatcher::test_generated numba/tests/test_dispatcher.py::TestGeneratedDispatcher::test_signature_errors numba/tests/test_dispatcher.py::TestDispatcherMethods::test_inspect_asm numba/tests/test_dispatcher.py::TestDispatcherMethods::test_inspect_cfg numba/tests/test_dispatcher.py::TestDispatcherMethods::test_inspect_cfg_with_python_wrapper numba/tests/test_dispatcher.py::TestDispatcherMethods::test_inspect_llvm numba/tests/test_dispatcher.py::TestDispatcherMethods::test_inspect_types numba/tests/test_dispatcher.py::TestDispatcherMethods::test_issue_with_array_layout_conflict numba/tests/test_dispatcher.py::TestDispatcherMethods::test_recompile numba/tests/test_dispatcher.py::TestDispatcherMethods::test_recompile_signatures numba/tests/test_dispatcher.py::TestCache::test_big_array numba/tests/test_dispatcher.py::TestCache::test_cache_invalidate numba/tests/test_dispatcher.py::TestCache::test_cache_reuse numba/tests/test_dispatcher.py::TestCache::test_caching numba/tests/test_dispatcher.py::TestCache::test_caching_nrt_pruned numba/tests/test_dispatcher.py::TestCache::test_closure numba/tests/test_dispatcher.py::TestCache::test_ctypes numba/tests/test_dispatcher.py::TestCache::test_inner_then_outer numba/tests/test_dispatcher.py::TestCache::test_looplifted numba/tests/test_dispatcher.py::TestCache::test_no_caching numba/tests/test_dispatcher.py::TestCache::test_outer_then_inner numba/tests/test_dispatcher.py::TestCache::test_recompile numba/tests/test_dispatcher.py::TestCache::test_same_names numba/tests/test_dispatcher.py::TestCacheWithCpuSetting::test_user_set_cpu_features numba/tests/test_dispatcher.py::TestCacheWithCpuSetting::test_user_set_cpu_name numba/tests/test_dispatcher.py::TestMultiprocessCache::test_multiprocessing numba/tests/test_dispatcher.py::TestCacheFileCollision::test_file_location numba/tests/test_dispatcher.py::TestCacheFileCollision::test_no_collision numba/tests/test_dispatcher.py::TestDispatcherFunctionBoundaries::test_dispatcher_as_arg_usecase numba/tests/test_dispatcher.py::TestDispatcherFunctionBoundaries::test_dispatcher_cannot_return_to_python numba/tests/test_dispatcher.py::TestDispatcherFunctionBoundaries::test_dispatcher_in_sequence_arg numba/tests/test_dispatcher.py::TestDispatcherFunctionBoundaries::test_pass_dispatcher_as_arg numba/tests/test_dispatcher.py::TestBoxingDefaultError::test_box_runtime_error numba/tests/test_dispatcher.py::TestBoxingDefaultError::test_unbox_runtime_error numba/tests/test_extending.py::TestLowLevelExtending::test_cast_mydummy numba/tests/test_extending.py::TestLowLevelExtending::test_func1 numba/tests/test_extending.py::TestLowLevelExtending::test_func1_isolated numba/tests/test_extending.py::TestPandasLike::test_index_get_data numba/tests/test_extending.py::TestPandasLike::test_index_getitem numba/tests/test_extending.py::TestPandasLike::test_index_is_monotonic numba/tests/test_extending.py::TestPandasLike::test_index_len numba/tests/test_extending.py::TestPandasLike::test_index_ufunc numba/tests/test_extending.py::TestPandasLike::test_series_clip numba/tests/test_extending.py::TestPandasLike::test_series_constructor numba/tests/test_extending.py::TestPandasLike::test_series_get_index numba/tests/test_extending.py::TestPandasLike::test_series_len numba/tests/test_extending.py::TestPandasLike::test_series_ufunc numba/tests/test_extending.py::TestHighLevelExtending::test_len numba/tests/test_extending.py::TestHighLevelExtending::test_no_cpython_wrapper numba/tests/test_extending.py::TestHighLevelExtending::test_print numba/tests/test_extending.py::TestHighLevelExtending::test_where numba/tests/test_extending.py::TestOverloadMethodCaching::test_caching_overload_method numba/tests/test_extending.py::TestIntrinsic::test_ll_pointer_cast numba/tests/test_extending.py::TestIntrinsic::test_serialization numba/tests/test_extending.py::TestRegisterJitable::test_flags_no_nrt numba/tests/test_extending.py::TestRegisterJitable::test_no_flags
numba/tests/test_dispatcher.py::TestCache::test_frozen numba/tests/test_extending.py::TestImportCythonFunction::test_missing_module
numba/config.py numba/dispatcher.py docs/source/reference/envvars.rst numba/extending.py
numba/config.py numba/dispatcher.py docs/source/reference/envvars.rst numba/extending.py