Skip to main content
Version: 1.1.1

printExprWithStats

Pollux 在查询执行期间收集大量统计信息,使我们能够 推断执行动态并排查性能问题。 最初,这些统计信息是在运算符级别收集的,并通过 Task::taskStats() 访问。最近,我们开始收集单个表达式的运行时统计信息,并引入了一个辅助函数 printExprWithStats,用于 打印带有运行时统计信息注释的表达式树。这使我们能够 识别单个函数中的瓶颈。

printExprWithStats

这是一个辅助方法,可用于打印带有每个表达式的运行时统计信息注释的表达式树。这些统计信息包括 CPU 时间和已处理的行数。

例如,对于一个表达式 (c0 + 3) * c1,printExprWithStats 的输出可能如下所示,该表达式对 1024 行求值,并使用平面向量表示。


multiply [cpu time: 52.77us, rows: 1024] -> BIGINT [#1]
plus [cpu time: 83.77us, rows: 1024] -> BIGINT [#2]
c0 [cpu time: 0ns, rows: 0] -> BIGINT [#3]
3:BIGINT [cpu time: 0ns, rows: 0] -> BIGINT [#4]
c1 [cpu time: 0ns, rows: 0] -> BIGINT [#5]

如果对字典编码数据执行相同的表达式,统计数据将显示执行处理的行数(即具有唯一值的行数)更少,并且占用的 CPU 时间也更少:


multiply [cpu time: 21.46us, rows: 205] -> BIGINT [#1]
plus [cpu time: 32.34us, rows: 205] -> BIGINT [#2]
c0 [cpu time: 0ns, rows: 0] -> BIGINT [#3]
3:BIGINT [cpu time: 0ns, rows: 0] -> BIGINT [#4]
c1 [cpu time: 0ns, rows: 0] -> BIGINT [#5]

printExprWithStats 可用于探索表达式求值中的各种优化,并调查这些优化是否适用于特定运行。

例如,我们可以使用包含两个表达式的表达式集来演示公共子表达式的消除:(c0 + c1) % 5, (c0 + c1) % 3。其中, (c0 + c1) 是一个仅求值一次的公共子表达式。printExprWithStats 的输出显示第二个实例是重复的。它带有 [CSE #2] 注释,这使我们能够识别原始表达式。 重复表达式的运行时统计信息不会显示。


mod [cpu time: 49.98us, rows: 1024] -> BIGINT [#1]
plus [cpu time: 53.75us, rows: 1024] -> BIGINT [#2]
c0 [cpu time: 0ns, rows: 0] -> BIGINT [#3]
c1 [cpu time: 0ns, rows: 0] -> BIGINT [#4]
5:BIGINT [cpu time: 0ns, rows: 0] -> BIGINT [#5]

mod [cpu time: 53.46us, rows: 1024] -> BIGINT [#6]
plus(c0, c1) -> BIGINT [CSE #2]
3:BIGINT [cpu time: 0ns, rows: 0] -> BIGINT [#7]

另一个例子是字典记忆化。如果我们使用相同的基础字典对同一列的多个批次数据求值一个表达式,求值过程会记住已处理的字典行的结果,并仅对新行求值。

例如,我们对 3 个批次数据求值了 log2(c0) * 1.5 个表达式。每个批次都有 1024 行字典,使用相同的字典进行编码。字典也包含 1024 行。 第一个批次引用了字典的前 1/5 行,每行重复 5 次。第二个批次引用了字典的前 1/3 行,每行重复 3 次。第三个批次引用了字典的前 1/10 行,每行重复 10 次。

在对第一个批次求值后,printExprWithStats 显示该表达式已在 205 行(1024 行的 1/5)上求值。


multiply [cpu time: 29.63us, rows: 205] -> DOUBLE [#1]
log2 [cpu time: 45.80us, rows: 205] -> DOUBLE [#2]
c0 [cpu time: 0ns, rows: 0] -> DOUBLE [#3]
1.5:DOUBLE [cpu time: 0ns, rows: 0] -> DOUBLE [#4]

在对第二批数据进行求值后,printExprWithStats 显示该表达式已在另外 137 行(137 = 1024 的 ⅓ - ⅕)上求值。这是预料之中 的,因为我们已经计算了前 205 行的结果,只需要计算剩余 137 行的结果。


multiply [cpu time: 46.30us, rows: 342] -> DOUBLE [#1]
log2 [cpu time: 68.59us, rows: 342] -> DOUBLE [#2]
c0 [cpu time: 0ns, rows: 0] -> DOUBLE [#3]
1.5:DOUBLE [cpu time: 0ns, rows: 0] -> DOUBLE [#4]

在对第三批数据进行求值后,printExprWithStats 显示该表达式 未在任何其他行上进行求值。这是预料之中的,因为我们已经 计算了所有必要的行的结果。


multiply [cpu time: 46.30us, rows: 342] -> DOUBLE [#1]
log2 [cpu time: 68.59us, rows: 342] -> DOUBLE [#2]
c0 [cpu time: 0ns, rows: 0] -> DOUBLE [#3]
1.5:DOUBLE [cpu time: 0ns, rows: 0] -> DOUBLE [#4]

ExprSetListener

可以注册一个监听器,在 ExprSet 销毁时调用,并接收整个表达式树的聚合运行时统计信息。各个表达式的统计信息根据表达式名称(例如函数名称或内置表达式名称,例如 AND、OR、IF、SWITCH 等)组合而成。该监听器可用于记录运行时统计信息以供离线分析。


/// Listener invoked on ExprSet destruction.
class ExprSetListener {
public:
virtual ~ExprSetListener() = default;

/// Called on ExprSet destruction. Provides runtime statistics about
/// expression evaluation.
/// @param uuid Universally unique identifier of the set of expressions.
/// @param event Runtime stats.
virtual void onCompletion(
const std::string& uuid,
const ExprSetCompletionEvent& event) = 0;
};

/// Register a listener to be invoked on ExprSet destruction. Returns true if
/// listener was successfully registered, false if listener is already
/// registered.
bool registerExprSetListener(std::shared_ptr<ExprSetListener> listener);