Spenserrrr commented on code in PR #59246:
URL: https://github.com/apache/spark/pull/59246#discussion_r4251448633


##########
python/pyspark/worker.py:
##########
@@ -2382,45 +2384,72 @@ def _evaluate_batch_udf(udf_func, rows):
                 return list(pool.map(lambda row: udf_func(*row), rows))
 
         def func(split_index: int, data: Iterator[pa.RecordBatch]) -> 
Iterator[pa.RecordBatch]:
-            for input_batch in data:
-                num_rows = input_batch.num_rows
-
-                # --- Input: Arrow -> Python columns ---
-                columns = [
-                    (
-                        [conv(v) for v in 
ArrowTableToRowsConversion._to_pylist(col)]
-                        if conv is not None
-                        else ArrowTableToRowsConversion._to_pylist(col)
-                    )
-                    for col, conv in zip(input_batch.itercolumns(), 
arrow_to_py_converters)
-                ]
-                if not columns:
-                    columns = [[_NoValue] * num_rows]
-
-                # --- Process: evaluate each UDF row-by-row ---
-                output_arrays = []
-                for udf_func, offsets, zero_arg, arrow_return_type, 
result_conv in udf_infos:
-                    rows = (
-                        [() for _ in range(num_rows)]
-                        if zero_arg
-                        else list(zip(*[columns[o] for o in offsets]))
-                    )
-                    results = _evaluate_batch_udf(udf_func, rows)
-                    verify_result_row_count(len(results), num_rows)
-
-                    # --- Output: Python -> Arrow ---
-                    converted = (
-                        [result_conv(r) for r in results] if result_conv is 
not None else results
-                    )
-                    try:
-                        arr = pa.array(converted, type=arrow_return_type)
-                    except pa.lib.ArrowInvalid:
-                        arr = pa.array(converted).cast(
-                            target_type=arrow_return_type, 
safe=runner_conf.safecheck
+            # Keep phase totals local so each metric is updated once instead 
of per batch.
+            input_duration_ns = 0
+            udf_duration_ns = 0
+            output_duration_ns = 0
+            clock = time.perf_counter_ns
+            try:
+                for input_batch in data:
+                    input_start_ns = clock()
+                    # --- Input: Arrow -> Python columns ---
+                    metrics.increment("pythonNumTimedBatches")
+                    num_rows = input_batch.num_rows
+                    columns = [
+                        (
+                            [conv(v) for v in 
ArrowTableToRowsConversion._to_pylist(col)]
+                            if conv is not None
+                            else ArrowTableToRowsConversion._to_pylist(col)
                         )
-                    output_arrays.append(arr)
+                        for col, conv in zip(input_batch.itercolumns(), 
arrow_to_py_converters)
+                    ]
+                    if not columns:
+                        columns = [[_NoValue] * num_rows]
+                    output_arrays: list[pa.Array] = []
+                    # The UDF loop replaces this when output preparation 
begins.
+                    output_start_ns = input_start_ns
+
+                    # --- Process: evaluate each UDF row-by-row ---
+                    for udf_func, offsets, zero_arg, arrow_return_type, 
result_conv in udf_infos:
+                        if output_arrays:
+                            # One boundary ends the previous output and starts 
the next input.
+                            input_start_ns = clock()
+                            output_duration_ns += input_start_ns - 
output_start_ns

Review Comment:
   Yeah it is a bit hard to follow. It reused one clock reading to end the 
previous UDF’s output and start the next UDF’s input. I do this to save a clock 
read, and the tradeoff is that it includes the loop transition to output time. 
I removed the placeholder output_start_ns = input_start_ns and now record each 
UDF’s output time in its own iteration. With one UDF, the worker still makes 
four clock reads per batch. With multiple UDFs, it uses one additional read to 
leave the loop time out. The final UDF’s output includes batch assembly. Do you 
think this is better?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to