Skip to content

perf: Add fast path for Version to Version comparison by skipping _key property#1083

Open
notatallshaw wants to merge 3 commits intopypa:mainfrom
notatallshaw:speed-up-sorting-by-fast-pathing-comparison
Open

perf: Add fast path for Version to Version comparison by skipping _key property#1083
notatallshaw wants to merge 3 commits intopypa:mainfrom
notatallshaw:speed-up-sorting-by-fast-pathing-comparison

Conversation

@notatallshaw
Copy link
Member

I was looking at #1082 to see if there were any performance gains to be made by implementing a simple comparison method, but almost all the performance improvements I found were related to skipping the _key property access.

I really don't understand why accessing the _key property is so slow, but sorted(versions) on a list of Versions I found to be consistently 10-20% faster on Python 3.14.

I'm raising for draft right now until I do some more research on why this has such as big impact and if there's a simpler way to get the gains.

@notatallshaw notatallshaw changed the title perf: Add fast path for Version to Version comparison by skipping _key property perf: Add fast path for Version to Version comparison by skipping _key property Feb 8, 2026
@henryiii
Copy link
Contributor

Ohhh, interesting. You are just skipping the property lookup. I tried this, but instead I avoided _key entirely and tried to just implement the comparison directly without creating an extra tuple, but it was much slower than tuple comparisons (though maybe I was missing something, I didn't write out all the methods, but sorting only uses one anyway).

If we really have to write out a lot more code that also needs to be maintained, we should be pretty sure the performance is worth it. Most changes so far haven't added too much code.

@notatallshaw
Copy link
Member Author

notatallshaw commented Feb 10, 2026

If we really have to write out a lot more code that also needs to be maintained, we should be pretty sure the performance is worth it. Most changes so far haven't added too much code.

Agreed, 20% improvement in sorting versions really is worth it though, this is something you need to do often.

But I do want to really understand if this is the only way to achieve this performance benefit, or if there's a simpler approach, also if this benefit is consistent across Python versions.

@henryiii
Copy link
Contributor

uv run asv continuous main pull/1083/head --sort default
Change Before [77a4f3f] After [88587fb] <pull/1083/head> Ratio Benchmark (Parameter)
- 60.7±0.06μs 54.0±0.3μs 0.89 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.11]
- 4.54±0.01ms 3.79±0.01ms 0.84 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.10]
- 3.49±0.05ms 2.76±0.01ms 0.79 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.11]
- 4.75±0.03ms 4.07±0.01ms 0.86 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.8]
- 4.66±0ms 4.01±0.01ms 0.86 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.9]

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.

Okay, not showing 3.12+. is odd, rerunning with --no-only-changed and manually extracting things that look different:

Change Before [77a4f3f] After [88587fb] <pull/1083/head> Ratio Benchmark (Parameter)
611±4μs 627±9μs 1.03 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.10]
453±2μs 461±3μs 1.02 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.11]
452±0.8μs 481±3μs 1.06 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.12]
416±2μs 444±2μs 1.07 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.13]
411±3μs 433±2μs 1.05 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.14]
596±1μs 626±1μs 1.05 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.8]
610±2μs 667±3μs 1.09 resolver.TimeResolverSuite.time_resolver_loop [PU-H2WF61JRQ6NY/virtualenv-py3.9]
4.00±0.02ms 4.28±0.02ms 1.07 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.10]
2.92±0.01ms 3.09±0.05ms 1.06 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.11]
3.13±0.02ms 3.27±0.02ms 1.04 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.12]
2.83±0.05ms 2.97±0.04ms 1.05 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.13]
+ 2.83±0.02ms 3.11±0.01ms 1.1 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.14]
3.95±0.05ms 4.25±0.01ms 1.07 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.8]
4.07±0.07ms 4.37±0.01ms 1.07 specifiers.TimeSpecSuite.time_contains [PU-H2WF61JRQ6NY/virtualenv-py3.9]
82.3±0.09μs 76.1±0.4μs 0.93 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.10]
- 60.2±0.08μs 53.6±0.09μs 0.89 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.11]
47.6±0.02μs 46.3±0.2μs 0.97 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.12]
42.7±0.03μs 40.6±0.1μs 0.95 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.13]
39.3±0.06μs 37.8±0.03μs 0.96 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.14]
83.9±0.4μs 79.6±3μs 0.95 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.8]
85.2±0.4μs 78.9±0.1μs 0.93 specifiers.TimeSpecSuite.time_filter [PU-H2WF61JRQ6NY/virtualenv-py3.9]
- 4.52±0.01ms 3.79±0.01ms 0.84 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.10]
- 3.49±0.04ms 2.76±0ms 0.79 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.11]
3.40±0.03ms 3.20±0ms 0.94 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.12]
3.09±0.01ms 2.86±0.01ms 0.92 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.13]
3.16±0.01ms 2.95±0.02ms 0.93 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.14]
- 4.74±0.01ms 4.28±0.3ms 0.9 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.8]
- 4.67±0.01ms 4.01±0.01ms 0.86 version.TimeVersionSuite.time_sort [PU-H2WF61JRQ6NY/virtualenv-py3.9]

@notatallshaw
Copy link
Member Author

notatallshaw commented Feb 11, 2026

Actually, I think there's still quite a bit of overhead from the isinstance check, which is maybe where the variation is coming from.

I suspect the fast path will be even faster pathier by changing the check from isinstance(other, Version) to type(other) is Version.

I'll try and play around with that later.

@henryiii
Copy link
Contributor

FYI, you can run the benchmarks by checking out #1059 and then using the commands above (or your branch name to test un-pushed changes).

@notatallshaw
Copy link
Member Author

@henryiii thanks, that showed my idea about isinstance(other, Version) to type(other) is Version made no difference.

I ran the benchmarks locally, but updated the sorting benchmark to only measure sorting performance (as I mention in #1059 (comment)) by adding self.version_objects = [Version(v) for v in self.valid_versions] in the set up and sorting self.version_objects.

Doing that, this is what I see with this branch:

| Change   | Before [2df7bdd8] <main>   | After [4a412248] <speed-up-sorting-by-fast-pathing-comparison>   |   Ratio | Benchmark (Parameter)                                                      |
|----------|----------------------------|------------------------------------------------------------------|---------|----------------------------------------------------------------------------|
| -        | 1.20±0ms                   | 1.02±0.03ms                                                      |    0.85 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.14] |
| -        | 1.19±0ms                   | 979±7μs                                                          |    0.82 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.13] |
| -        | 2.36±0.02ms                | 1.85±0.09ms                                                      |    0.78 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.9]  |
| -        | 1.47±0.01ms                | 1.10±0ms                                                         |    0.75 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.12] |
| -        | 2.49±0.01ms                | 1.87±0ms                                                         |    0.75 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.8]  |
| -        | 2.04±0.01ms                | 1.33±0ms                                                         |    0.65 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.10] |
| -        | 1.54±0.01ms                | 970±4μs                                                          |    0.63 | version.TimeVersionSuite.time_sort [You-Are-My-Sunshine/virtualenv-py3.11] |

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.

@notatallshaw notatallshaw force-pushed the speed-up-sorting-by-fast-pathing-comparison branch from 88587fb to 4a41224 Compare February 13, 2026 14:43
@henryiii
Copy link
Contributor

Sorting only uses one method, __lt__. I think you'd get the same performance benefit with just that one override instead of all six.

@notatallshaw
Copy link
Member Author

notatallshaw commented Feb 14, 2026

Sorting only uses one method, __lt__. I think you'd get the same performance benefit with just that one override instead of all six.

Yes, I implemented this cold/warm micro benchmark approach I mention in #1059 (comment) and I only need __lt__ for speeding up sorting.

However, I remembered different comparisons are used in specifiers, so I also implemented cold/warm micro benchmarks there as well (both cold/warm versions and cold/warm specifiers). For specifiers I see smaller but significantly significant improvement in filtering prior to Python 3.12, and in that case I need all the methods except __ne__. It seems that in Python 3.12 property access was made much faster, and while if I look at all results it does appear filtering still improves 5-10% in Python 3.12+, it becomes statistically hard to prove due to noise.

Here's my results:

| Change   | Before [2df7bdd8] <main>   | After [4a412248] <speed-up-sorting-by-fast-pathing-comparison>   |   Ratio | Benchmark (Parameter)                                                                  |
|----------|----------------------------|------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------|
| -        | 97.1±1μs                   | 88.2±1μs                                                         |    0.91 | specifiers.TimeSpecSuite.time_filter_cold_warm [You-Are-My-Sunshine/virtualenv-py3.10] |
| -        | 100±0.9μs                  | 91.1±0.4μs                                                       |    0.91 | specifiers.TimeSpecSuite.time_filter_warm_warm [You-Are-My-Sunshine/virtualenv-py3.9]  |
| -        | 108±0.5μs                  | 97.0±0.7μs                                                       |    0.9  | specifiers.TimeSpecSuite.time_filter_cold_warm [You-Are-My-Sunshine/virtualenv-py3.8]  |
| -        | 150±1μs                    | 134±0.7μs                                                        |    0.9  | specifiers.TimeSpecSuite.time_filter_warm_cold [You-Are-My-Sunshine/virtualenv-py3.8]  |
| -        | 106±1μs                    | 95.9±0.4μs                                                       |    0.9  | specifiers.TimeSpecSuite.time_filter_warm_warm [You-Are-My-Sunshine/virtualenv-py3.8]  |
| -        | 62.6±1μs                   | 55.4±0.3μs                                                       |    0.89 | specifiers.TimeSpecSuite.time_filter_warm_warm [You-Are-My-Sunshine/virtualenv-py3.11] |
| -        | 64.4±2μs                   | 56.3±0.5μs                                                       |    0.88 | specifiers.TimeSpecSuite.time_filter_cold_warm [You-Are-My-Sunshine/virtualenv-py3.11] |
| -        | 95.8±0.9μs                 | 84.8±0.5μs                                                       |    0.88 | specifiers.TimeSpecSuite.time_filter_warm_warm [You-Are-My-Sunshine/virtualenv-py3.10] |
| -        | 1.52±0ms                   | 1.33±0.01ms                                                      |    0.88 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.14]        |
| -        | 1.50±0.01ms                | 1.26±0ms                                                         |    0.83 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.13]        |
| -        | 1.28±0.01ms                | 1.07±0.01ms                                                      |    0.83 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.14]        |
| -        | 1.26±0ms                   | 1.03±0ms                                                         |    0.81 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.13]        |
| -        | 1.83±0.01ms                | 1.44±0.01ms                                                      |    0.79 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.12]        |
| -        | 3.13±0.2ms                 | 2.46±0.01ms                                                      |    0.78 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.8]         |
| -        | 2.87±0.01ms                | 2.24±0.01ms                                                      |    0.78 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.9]         |
| -        | 1.58±0.02ms                | 1.17±0.01ms                                                      |    0.74 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.12]        |
| -        | 2.62±0.02ms                | 1.93±0.02ms                                                      |    0.74 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.8]         |
| -        | 2.41±0.02ms                | 1.77±0.04ms                                                      |    0.73 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.9]         |
| -        | 2.60±0.01ms                | 1.87±0.06ms                                                      |    0.72 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.10]        |
| -        | 1.91±0.01ms                | 1.29±0.01ms                                                      |    0.67 | version.TimeVersionSuite.time_sort_cold [You-Are-My-Sunshine/virtualenv-py3.11]        |
| -        | 2.24±0.03ms                | 1.41±0.01ms                                                      |    0.63 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.10]        |
| -        | 1.64±0.01ms                | 1.03±0ms                                                         |    0.63 | version.TimeVersionSuite.time_sort_warm [You-Are-My-Sunshine/virtualenv-py3.11]        |

@notatallshaw notatallshaw marked this pull request as ready for review February 14, 2026 15:38
@notatallshaw
Copy link
Member Author

This is probably the change that makes accessing the property much faster on Python 3.12+: python/cpython#93912.

Comment on lines +551 to +552
def __hash__(self) -> int:
return hash(self._key)
Copy link
Contributor

@henryiii henryiii Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here (and also in the base class written exactly the same way)?

def __hash__(self) -> int:
return hash(self._key)

# Override comparison methods to use direct _key_cache access
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Override comparison methods to use direct _key_cache access
# Override comparison methods to use direct _key_cache access
# This is faster than property access, especially before Python 3.12

@henryiii
Copy link
Contributor

Strange, I tried a variation of this, by using a method instead:

+    # Property access is slower, especially on Python < 3.12
+    def _get_key(self) -> CmpKey:
+        if self._key_cache is None:
+            self._key_cache = _cmpkey(
+                self._epoch,
+                self._release,
+                self._pre,
+                self._post,
+                self._dev,
+                self._local,
+            )
+        return self._key_cache
+
     def __hash__(self) -> int:
         return hash(self._key)

     # Override comparison methods to use direct _key_cache access
     def __lt__(self, other: _BaseVersion) -> bool:
         if isinstance(other, Version):
-            if self._key_cache is None:
-                self._key_cache = _cmpkey(
-                    self._epoch,
-                    self._release,
-                    self._pre,
-                    self._post,
-                    self._dev,
-                    self._local,
-                )
-            if other._key_cache is None:
-                other._key_cache = _cmpkey(
-                    other._epoch,
-                    other._release,
-                    other._pre,
-                    other._post,
-                    other._dev,
-                    other._local,
-                )
-            return self._key_cache < other._key_cache
+            return self._get_key() < other._get_key()

(and likewise for the other methods). This was slower, except for complex filtering, where it was all over the place:

Change Before [4a41224] <speed-up-sorting-by-fast-pathing-comparison~1> After [acdabe8d] Ratio Benchmark (Parameter)
515±0.9μs 586±400μs ~1.14 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.10]
+ 168±6μs 535±100μs 3.18 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.11]
+ 349±100μs 644±100μs 1.84 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.12]
258±100μs 425±300μs ~1.65 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.13]
547±300μs 391±200μs ~0.71 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.14]
425±200μs 585±300μs ~1.38 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.8]
373±100μs 420±200μs ~1.12 specifiers.TimeSpecSuite.time_filter_complex [PU-H2WF61JRQ6NY/virtualenv-py3.9]
+ 74.5±0.2μs 83.3±1μs 1.12 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.10]
53.3±0.4μs 56.4±0.5μs 1.06 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.11]
46.0±0.4μs 47.2±0.06μs 1.03 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.12]
39.9±0.4μs 42.5±0.05μs 1.06 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.13]
59.3±0.3μs 63.2±0.6μs 1.07 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.14]
+ 76.1±0.2μs 85.3±0.7μs 1.12 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.8]
+ 77.5±0.2μs 85.6±2μs 1.10 specifiers.TimeSpecSuite.time_filter_simple [PU-H2WF61JRQ6NY/virtualenv-py3.9]
+ 1.71±0ms 2.43±0ms 1.42 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.10]
+ 1.34±0.04ms 1.71±0ms 1.28 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.11]
+ 1.45±0.01ms 1.73±0ms 1.20 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.12]
+ 1.24±0.01ms 1.54±0ms 1.24 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.13]
+ 1.83±0ms 2.23±0ms 1.21 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.14]
+ 2.03±0ms 2.74±0.07ms 1.35 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.8]
+ 2.02±0ms 2.69±0.03ms 1.33 version.TimeVersionSuite.time_sort_cold [PU-H2WF61JRQ6NY/virtualenv-py3.9]
+ 1.33±0ms 2.05±0ms 1.54 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.10]
+ 1.06±0ms 1.45±0ms 1.37 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.11]
+ 1.20±0.01ms 1.45±0.01ms 1.20 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.12]
+ 1.02±0ms 1.32±0ms 1.30 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.13]
+ 1.52±0ms 1.90±0ms 1.25 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.14]
+ 1.66±0.01ms 2.32±0.01ms 1.40 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.8]
+ 1.60±0ms 2.28±0.02ms 1.42 version.TimeVersionSuite.time_sort_warm [PU-H2WF61JRQ6NY/virtualenv-py3.9]

@notatallshaw
Copy link
Member Author

notatallshaw commented Feb 16, 2026

Thanks for the review, I will address shortly.

Strange, I tried a variation of this, by using a method instead:

Yeah, my current speculation is that even method look up in a sort adds measurable overhead, and on Python 3.12+ it's this performance that is being saved, not specifically that it's a property look up.

@henryiii
Copy link
Contributor

henryiii commented Feb 16, 2026

I'm not sure why it's so variable based on Python version on that one benchmark (or why that benchmark seems so variable in the first place, need to look at it).

I also tried a free function to avoid descriptors,

def _get_key(ver: Version, /) -> CmpKey:
    if ver._key_cache is None:
        ver._key_cache = _cmpkey(
            ver._epoch,
            ver._release,
            ver._pre,
            ver._post,
            ver._dev,
            ver._local,
        )
    return ver._key_cache

and results are similar, sorting is much slower (20-30 percent), and time_filter_complex is all over the place.

@henryiii
Copy link
Contributor

henryiii commented Mar 3, 2026

Did you want to address the two comments above? :)

@notatallshaw
Copy link
Member Author

Did you want to address the two comments above? :)

Yes, but I have very low capacity to work on open source code right now, hopefully be back to doing OSS code soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants