Copilot commented on code in PR #13368:
URL: https://github.com/apache/gluten/pull/13368#discussion_r4314007731


##########
backends-velox/src/test/scala/org/apache/spark/sql/execution/joins/GlutenParallelBroadcastJoinSuite.scala:
##########
@@ -32,36 +33,113 @@ class GlutenParallelBroadcastJoinSuite extends 
VeloxWholeStageTransformerSuite {
     .set("spark.gluten.velox.broadcast.build.targetBytesPerThread", "8KB")
     
.set("spark.gluten.sql.columnar.backend.velox.broadcastHashTableBuildThreads", 
"4")
 
-  for (buildOnDriver <- Seq(false, true)) {
-    test(s"parallel broadcast hash table build with IO disabled, 
driver=$buildOnDriver") {
-      withSQLConf(
-        
"spark.gluten.sql.columnar.backend.velox.driverSideBroadcastHashTableBuild" ->
-          buildOnDriver.toString,
-        "spark.gluten.velox.minTableRowsForParallelJoinBuild" -> "1"
-      ) {
-        withTable("parallel_broadcast_build") {
-          spark.range(4096)
-            .selectExpr("id", "sha2(cast(id as string), 256) AS value")
-            .write.saveAsTable("parallel_broadcast_build")
-
-          val result = spark.sql("""
-                                   |SELECT /*+ BROADCAST(b) */ p.id, b.value
-                                   |FROM parallel_broadcast_build p JOIN 
parallel_broadcast_build b
-                                   |ON p.id = b.id
-                                   |""".stripMargin)
-          val expected = 
spark.table("parallel_broadcast_build").collect().toSeq
-          checkAnswer(result, expected)
-
-          val plan = result.queryExecution.executedPlan
-          assert(plan.collect { case j: BroadcastHashJoinExecTransformer => j 
}.nonEmpty)
-          val exchanges = plan.collect { case e: ColumnarBroadcastExchangeExec 
=> e }
-          assert(exchanges.size == 1)
-          assert(exchanges.head.metrics("buildThreads").value > 1)
-          if (buildOnDriver) {
-            assert(exchanges.head.metrics("serializedHashTableSize").value > 0)
+  private val threadsKey =
+    "spark.gluten.sql.columnar.backend.velox.broadcastHashTableBuildThreads"
+  private val taskSlotsKey = "spark.gluten.numTaskSlotsPerExecutor"
+
+  test("broadcast build threads: resolve process defaults without modifying 
SparkConf") {
+    val conf = new SparkConf(false)
+      .setMaster("yarn")
+      .set("spark.driver.cores", "6")
+      .set("spark.executor.cores", "16")
+      .set("spark.task.cpus", "2")
+      .set(taskSlotsKey, "8")
+    val original = conf.getAll.toMap
+
+    assert(VeloxListenerApi.parseConf(conf, isDriver = true)(threadsKey) == 
"6")
+    assert(conf.getAll.toMap == original)
+    assert(VeloxListenerApi.parseConf(conf, isDriver = false)(threadsKey) == 
"8")
+    assert(conf.getAll.toMap == original)
+  }
+
+  test("broadcast build threads: explicit thread count takes precedence on 
both " +
+    "driver and executor") {
+    val conf = new SparkConf(false)
+      .set("spark.driver.cores", "6")
+      .set(taskSlotsKey, "8")
+      .set(threadsKey, "3")
+
+    Seq(true, false).foreach {
+      isDriver => assert(VeloxListenerApi.parseConf(conf, 
isDriver)(threadsKey) == "3")
+    }
+    assert(conf.get(threadsKey) == "3")
+  }
+
+  test("broadcast build threads: missing resource settings and spark.master 
default " +
+    "to one thread") {
+    Seq(true, false).foreach {
+      isDriver =>
+        assert(VeloxListenerApi.parseConf(new SparkConf(false), 
isDriver)(threadsKey) == "1")
+    }
+  }
+
+  test(
+    "broadcast build threads: driver defaults to one core independently of 
executor task slots") {
+    val conf = new SparkConf(false).set(taskSlotsKey, "8")
+    assert(VeloxListenerApi.parseConf(conf, isDriver = true)(threadsKey) == 
"1")
+  }
+
+  test("broadcast build threads: local pool uses master cores instead of task 
slots") {
+    val masters = Seq(
+      "local" -> 1,
+      "local[6]" -> 6,
+      "local[6,2]" -> 6,
+      "local[*]" -> Runtime.getRuntime.availableProcessors(),
+      "local[*,2]" -> Runtime.getRuntime.availableProcessors()
+    )

Review Comment:
   Using `Runtime.getRuntime.availableProcessors()` in assertions can be 
brittle across CI environments (e.g., container CPU limits, differing JVM 
visibility) and can drift from Spark’s own parsing of `local[*]`. To make the 
test robust and aligned with production logic, compute the expected value using 
the same code path used in `parseConf` for local mode (e.g., 
`SparkResourceUtil.getExecutorCores(conf)` after setting `master`).



##########
cpp/velox/compute/VeloxBackend.cc:
##########
@@ -255,9 +255,16 @@ void VeloxBackend::init(
         std::make_unique<folly::CPUThreadPoolExecutor>(ioThreads, 
folly::CPUThreadPoolExecutor::makeLifoSemQueue());
   }
 
-  const auto broadcastHashTableBuildThreads =
-      std::max<int32_t>(1, 
backendConf_->get<int32_t>(kBroadcastHashTableBuildThreads, 
numTaskSlotsPerExecutor));
-  broadcastHashTableBuildExecutor_ = 
std::make_unique<folly::CPUThreadPoolExecutor>(broadcastHashTableBuildThreads);
+  const int32_t broadcastHashTableBuildThreads = [&]() {
+    if (!backendConf_->valueExists(kBroadcastHashTableBuildThreads)) {
+      LOG(WARNING) << kBroadcastHashTableBuildThreads << " is not set. Falling 
back to " << kNumTaskSlotsPerExecutor
+                   << " (" << numTaskSlotsPerExecutor << ").";
+      return numTaskSlotsPerExecutor;
+    }
+    return backendConf_->get<int32_t>(kBroadcastHashTableBuildThreads).value();
+  }();
+  broadcastHashTableBuildExecutor_ =
+      std::make_unique<folly::CPUThreadPoolExecutor>(std::max<int32_t>(1, 
broadcastHashTableBuildThreads));

Review Comment:
   Logging this as `WARNING` whenever the setting is absent can be noisy in 
native embedding scenarios (where omitting the key is expected per the comment 
in `VeloxConfig.h`). Consider downgrading this to `INFO`/`VLOG`, or gating it 
(e.g., log once) so normal defaulting doesn’t produce warning-level logs.



##########
cpp/velox/tests/VeloxBackendTest.cc:
##########
@@ -18,31 +18,56 @@
 #include <folly/executors/CPUThreadPoolExecutor.h>
 #include <folly/futures/Future.h>
 #include <gtest/gtest.h>
+#include <optional>
+#include <string>
 #include <thread>
+#include <unordered_map>
 
 #include "compute/VeloxBackend.h"
 #include "config/VeloxConfig.h"
 
 namespace gluten {
 
-class VeloxBackendTest : public ::testing::Test {
+struct BroadcastBuildThreadsTestParam {
+  int taskSlots;
+  std::optional<int> buildThreads;
+  int expectedThreads;
+};
+
+class VeloxBackendTest : public 
::testing::TestWithParam<BroadcastBuildThreadsTestParam> {
  protected:
-  static void SetUpTestSuite() {
-    VeloxBackend::create(AllocationListener::noop(), 
{{kNumTaskSlotsPerExecutor, "0"}});
+  void SetUp() override {
+    const auto& param = GetParam();
+    std::unordered_map<std::string, std::string> 
conf{{kNumTaskSlotsPerExecutor, std::to_string(param.taskSlots)}};
+    if (param.buildThreads.has_value()) {
+      conf.emplace(kBroadcastHashTableBuildThreads, 
std::to_string(param.buildThreads.value()));
+    }
+    VeloxBackend::create(AllocationListener::noop(), conf);
   }
 
-  static void TearDownTestSuite() {
+  void TearDown() override {
     VeloxBackend::get()->tearDown();
   }
 };
 
-TEST_F(VeloxBackendTest, broadcastHashTableBuildExecutorWithZeroTaskSlots) {
+TEST_P(VeloxBackendTest, broadcastHashTableBuildExecutorThreadCount) {

Review Comment:
   The parameter and test names are a bit ambiguous relative to the PR’s 
driver-vs-executor defaulting semantics (these C++ tests are validating the 
native fallback behavior when the key is absent). Consider renaming to make 
intent explicit, e.g. `NativeBroadcastBuildThreadsFallbackParam` and 
`broadcastHashTableBuildThreads_nativeFallbackToTaskSlots`, so readers don’t 
confuse this with the Spark-side per-process default resolution.



##########
cpp/velox/tests/VeloxBackendTest.cc:
##########
@@ -18,31 +18,56 @@
 #include <folly/executors/CPUThreadPoolExecutor.h>
 #include <folly/futures/Future.h>
 #include <gtest/gtest.h>
+#include <optional>
+#include <string>
 #include <thread>
+#include <unordered_map>
 
 #include "compute/VeloxBackend.h"
 #include "config/VeloxConfig.h"
 
 namespace gluten {
 
-class VeloxBackendTest : public ::testing::Test {
+struct BroadcastBuildThreadsTestParam {
+  int taskSlots;
+  std::optional<int> buildThreads;
+  int expectedThreads;
+};

Review Comment:
   The parameter and test names are a bit ambiguous relative to the PR’s 
driver-vs-executor defaulting semantics (these C++ tests are validating the 
native fallback behavior when the key is absent). Consider renaming to make 
intent explicit, e.g. `NativeBroadcastBuildThreadsFallbackParam` and 
`broadcastHashTableBuildThreads_nativeFallbackToTaskSlots`, so readers don’t 
confuse this with the Spark-side per-process default resolution.



-- 
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