PERF: Try fast/slow paths only once in DataFrameGroupby.transform
State: closed

- [x] closes #41598
- [x] Ensure all linting tests pass, see [here](https://pandas.pydata.org/pandas-docs/dev/development/contributing.html#code-standards) for how to run them
- [x] whatsnew entry

Still need to run ASVs to measure performance, making sure CI passes first.

Edit: ASV results for 7b86fdb

```
       before           after         ratio
     [9f6a91ae]       [7b86fdb9]
     <groupby_transform_path~1>       <groupby_transform_path>
-     23.8±0.07ms         21.4±2ms     0.90  groupby.Nth.time_series_nth_all('float32')
-      11.8±0.1ms       10.4±0.6ms     0.88  groupby.Nth.time_frame_nth('object')
-      27.1±0.1ms       23.3±0.1ms     0.86  groupby.Nth.time_series_nth_all('object')
-      19.4±0.2ms       16.5±0.2ms     0.85  groupby.Nth.time_groupby_nth_all('float64')
-      21.7±0.2ms       18.4±0.3ms     0.85  groupby.Nth.time_frame_nth_any('float32')
-      24.1±0.1ms       20.2±0.2ms     0.84  groupby.Nth.time_series_nth_any('float64')
-      24.0±0.2ms      20.1±0.05ms     0.84  groupby.Nth.time_series_nth_all('float64')
-      56.4±0.4ms       20.1±0.4ms     0.36  groupby.TransformEngine.time_dataframe_cython(False)
-      56.5±0.4ms       20.1±0.4ms     0.36  groupby.TransformEngine.time_dataframe_cython(True)

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.
```

Labels:
name: Groupby
name: Performance
description: Memory or execution speed performance
name: Apply
description: Apply, Aggregate, Transform, Map

Comments:
@rhshadrach can you rebase
@jreback @jbrockmendel Updated and ready for review. 

I also tried _only_ using the slow path (instead of choosing between fast/slow); this resulted in a 0.7 ratio for the benchmark `groupby.TransformEngine.time_dataframe_cython(False)` as opposed to a 0.3 in this PR. While I'd prefer to find a way to not have to choose between different paths based on the first group, the performance seems worth it in my opinion.
> While I'd prefer to find a way to not have to choose between different paths based on the first group

IIUC you mean do some introspection on the function (or require users to specify somehow) to determine this instead of a try/except?  That would be nice.

> this resulted in a 0.7 ratio for the benchmark

What about for the groupby benchmarks more generally?  The slow path has been optimized a bunch this year (xref #40263)
I think its fine to determine how to call it based on the first group. we need *some* metric.
@jbrockmendel 

> IIUC you mean do some introspection on the function (or require users to specify somehow) to determine this instead of a try/except? That would be nice.

Introspection seems fragile to me. I didn't consider user specification but I like the idea as long as it isn't required. Another option would be to restrict what UDFs are acceptable (e.g. must return Series, DataFrame, or NumPy array, and maybe some other things). For this latter option, we could then handle everything acceptable via the fastpath and remove the slowpath entirely. But perhaps the flexibility of current handling makes this an unwelcome trade-off.

> What about for the groupby benchmarks more generally? The slow path has been optimized a bunch this year (xref #40263)

~The OP has all the groupby ASVs. I don't expect any difference in performance with the updates to this PR but we re-run tonight to check.~ I misread. Will run groupby ASVs using only slowpath tonight.
comment by @jbrockmendel ping on green
@jbrockmendel - This is slowpath only (L1385, first line of _choose_path is `return slow_path, slow_path(group)`):

```
       before           after         ratio
     [9ffc924e]       [462500f7]
     <whatsnew_contributing~2>       <groupby_transform_path>
+      17.9±0.6ms         25.4±1ms     1.42  groupby.Cumulative.time_frame_transform('int64', 'cummin')
+     21.3±0.07ms       26.5±0.5ms     1.24  groupby.Nth.time_frame_nth_any('datetime')
+      25.9±0.7ms       31.4±0.4ms     1.21  groupby.Nth.time_frame_nth_any('object')
+     11.9±0.09ms       14.0±0.3ms     1.18  groupby.Nth.time_frame_nth('object')
+      23.5±0.1ms         26.7±3ms     1.14  groupby.Nth.time_series_nth_any('datetime')
-      71.1±0.5ms       57.4±0.5ms     0.81  groupby.TransformEngine.time_dataframe_cython(True)
-      71.0±0.5ms       56.9±0.2ms     0.80  groupby.TransformEngine.time_dataframe_cython(False)

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE DECREASED.
```
@jreback - green
thanks @rhshadrach very nice!