Repository: vega/altair

Issue:
`alt.Chart.from_dict` slow due to large number of calls to `utils.schemaapi._use_referencing_library`
I'm the author of a [FOSS library](http://github.com/moj-analytical-services/splink) that makes extensive use of `alt.Chart.from_dict`.

I noticed this is quite slow, and I think I have identified the root cause. Specifically, `alt.Chart.from_dict` makes a very large number of calls to [`utils.schemaapi._use_referencing_library`](https://github.com/altair-viz/altair/blob/b1b9dba8a1cd11f9a5e18ed38d4fd5acf3961ca7/altair/utils/schemapi.py#L203). In turn, this makes repeated calls to `importlib`, resulting in more than half of its runtime in `importlib.metadata`

Since the result of `utils.schemaapi._use_referencing_library` is a bool and will not change within a Python session (it only changes if the user installs a different version of the `jsonschema` package), it can be computed a single time, resulting in significantly improved performace.

## Reprex

<details>
<summary>
Reprex including timings
</summary>

```python
import json
import time
import urllib.request

import altair as alt

spec_json_url = "https://gist.githubusercontent.com/RobinL/de91f5d95aa29ee0b5464bca4bfd3dcf/raw/c391db54295a8f43e3258aa6c42a8a39f239b4bc/match_weights.json"

with urllib.request.urlopen(spec_json_url) as url:
    data = json.loads(url.read().decode())

start_time = time.time()
chart = alt.Chart.from_dict(data)
end_time = time.time()
execution_time = end_time - start_time
print(f"Execution time: {execution_time} seconds")

spec_json_url = "https://gist.githubusercontent.com/RobinL/de91f5d95aa29ee0b5464bca4bfd3dcf/raw/c391db54295a8f43e3258aa6c42a8a39f239b4bc/waterfall.json"

with urllib.request.urlopen(spec_json_url) as url:
    data = json.loads(url.read().decode())

start_time = time.time()
chart = alt.Chart.from_dict(data)
end_time = time.time()
execution_time = end_time - start_time
print(f"Execution time: {execution_time} seconds")
```

</details>

The timings for the above on `main` are:

```
Execution time: 0.9597208499908447 seconds
Execution time: 1.2827000617980957 seconds
```

on a 2019 macbook pro.

After removing the repeated `importlib` calls, the timings are:

```
Execution time: 0.2081460952758789 seconds
Execution time: 0.3407268524169922 seconds
```

## Solution

I've opened a [PR](https://github.com/altair-viz/altair/pull/3383) that I think should address this


Discussion:

Resolving patch:
diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py
--- a/altair/utils/schemapi.py
+++ b/altair/utils/schemapi.py
@@ -71,6 +71,8 @@
 # class-level _class_is_valid_at_instantiation attribute to False
 DEBUG_MODE: bool = True
 
+jsonschema_version_str = importlib_version("jsonschema")
+
 
 def enable_debug_mode() -> None:
     global DEBUG_MODE
@@ -203,7 +205,6 @@ def _get_json_schema_draft_url(schema: dict) -> str:
 def _use_referencing_library() -> bool:
     """In version 4.18.0, the jsonschema package deprecated RefResolver in
     favor of the referencing library."""
-    jsonschema_version_str = importlib_version("jsonschema")
     return Version(jsonschema_version_str) >= Version("4.18")
 
 
diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py
--- a/tools/schemapi/schemapi.py
+++ b/tools/schemapi/schemapi.py
@@ -69,6 +69,8 @@
 # class-level _class_is_valid_at_instantiation attribute to False
 DEBUG_MODE: bool = True
 
+jsonschema_version_str = importlib_version("jsonschema")
+
 
 def enable_debug_mode() -> None:
     global DEBUG_MODE
@@ -201,7 +203,6 @@ def _get_json_schema_draft_url(schema: dict) -> str:
 def _use_referencing_library() -> bool:
     """In version 4.18.0, the jsonschema package deprecated RefResolver in
     favor of the referencing library."""
-    jsonschema_version_str = importlib_version("jsonschema")
     return Version(jsonschema_version_str) >= Version("4.18")
 
 


Test patch: