Doris开发手记5:一场链接引发“吊诡”的性能问题

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 在Doris中,一个奇怪的性能问题暴露了`cos`函数比`sin`快百倍。分析发现,`sin`因静态链接到低效实现,而非动态链接到优化的`libm.so`。通过`dlopen`和`dlsym`动态链接`libm.so`的`sin`解决了问题。问题源于`ldb_toolchain`的静态库,其中的`glibc-compatibility.a`错误地链接了`sin`函数。移除该库中的数学函数实现了修复。感谢社区成员的帮助,问题得以解决,恢复了Doris的高性能。

近期正在对 Doris 的性能问题展开排查,发现了一个极为“吊诡”的函数执行性能问题。经过一系列的CPU热点代码分析之后,发现“罪魁祸首”居然是libtoolchain中的静态库导致的。借用本篇手记记录下问题的发现,希望记录下一些对于C/C++程序链接问题的分析思路,也希望读者也能有所收获。

1.奇怪的性能问题

最近发现了一个奇怪的性能问题,Doris的cos函数居然比sin函数快了有百倍之多

sin函数的性能:
sin函数性能

cos函数的性能:

cos函数性能

于是实际分析了一下现有Doris的函数实现代码,发现无论是cos和sin都是调用了cmath的标准的std的函数,能有这么大的差距确实让人匪夷所思。

通过Perf工具来分析一下函数的CPU占用情况,来进一步分析是哪里出了问题?

奇怪的调用

通过汇编发现了这里的猫腻,果不其然,两个函数生成的汇编函数调用有着明显的区别

  • sin函数

sin函数的汇编调用

乍看之下,就是一个正常的函数跳转调用的过程,并无不妥之处。而真相藏在下面cos函数的调用汇编里。

  • cos函数

cos函数的汇编调用
在这里发现和上面sin函数调用的一个小区别,变成了callq cos@plt, 这里多出了@plt的标识是Procedure Linkage Table(过程链接表,简称PLT)。即每个在程序中被调用的外部函数在PLT中都有一个对应的条目。当程序首次调用某个共享库函数时,PLT会负责解析该函数的实际地址(如果尚未解析),然后通过跳转到该实际地址来执行函数。那就说明cos函数是动态链接调用的,而sin函数却是静态链接进来的。

分析:Doris的代码或链接库应该在某处实现了一个低效率的sin函数,奇怪的静态链接进来了,从而没有调用到真正libm.so里应该被动态链接的sin函数。 (可恶的C语言,函数没有命名空间和重载,链接问题解决起来简直是....)由于libm的标准库是基于平台且高度优化的,sin出现性能差也就不足为奇了。

2.验证想法,解决性能问题

好的,既然有了怀疑问题的点了,那么就开始研究解决方案。那这里的思路也很简单,既然cos函数是动态链接的,那莫不如笔者也强行动态链接一把,利用dlopen找到需要的sin函数来验证性能。

2.1 dlopen与dlsym

dlopen是POSIX标准库中的一个函数,用于在运行时动态地加载共享函数库。它是动态链接机制的一部分,允许程序在执行过程中根据需要加载库,而不是在编译时静态链接所有依赖的库。这对于模块化编程、插件系统、减少内存占用、以及在不重新编译整个程序的情况下更新库等功能非常有用。

void* dlopen(const char* filename, int flags);

dlsym 用于在运行时从已打开的动态链接库中查找并获取符号(比如函数)的地址。

void* dlsym(void* handle, const char* symbol);

通过二者的配合,我们可以打开一个动态链接库,并通过函数名提取对应的函数,这样就能够绕开静态链接的sin,调用到笔者期待的libm.so提供的sin函数了。

2.2 代码开发

思路是最难的,写代码永远是最简单的。直接上笔者修改Doris的代码吧:

struct UnaryFunctionPlainSin {
    using Type = DataTypeFloat64;
    static constexpr auto name = "sin";
    using FuncType = double (*)(double);

    static FuncType get_sin_func() {
        void* handle = dlopen("libm.so.6", RTLD_LAZY);
        if (handle) {
            if (auto sin_func = (double (*)(double))dlsym(handle, "sin"); sin_func) {
                return sin_func;
            }
            dlclose(handle);
        }
        return std::sin;
    }

    static void execute(const double* src, double* dst) {
        static auto sin_func = get_sin_func();
        *dst = sin_func(*src);
    }
};

代码很简单,简单解释一下就好:这里通过dlopen 打开libm.so的动态库, 并且用dlsym获取了对应sin函数的函数指针。那么后续直接用sin_func这个函数指针就可以调用到libm.so里的sin函数了。

果然,药到病除,Doris的性能再次起飞~~~

sin函数性能已经恢复正常

3. 刨根问底,发现链接之处

通过上一小节对代码性能的验证,进一步实锤了笔者的怀疑。那么接下来需要逮捕这个奇怪的sin函数的源头,究竟是从哪里链接进来的呢?

通过traceing 链接器的链接结果,发现了sin函数定位在了ldb_toolchain提供的静态库里glibc-compatibility.a

找到引入的静态库

于是在鸟哥的帮助之下, 找到了引入了sin函数commit,看起来musl库引入了sin函数的符号在链接顺序上提前链接在libm.sosin函数上,导致出现这次的性能乌龙。而后续ldb_toolchain剔除对应的数学库在glibc-compatibility.a上的实现,则能够彻底解决该问题。

image.png

碎碎念:幸好当时没顺手实现cos,否则笔者的问题定位起来会更加的扑朔迷离

4.小结

Bingo! 到此为止,问题顺利解决,又可以重新体验Doris的丝滑极速了。

本文特别鸣谢社区小伙伴:

  • @JackDrogon的指点分析
  • @amosbird的协助排查和耐心指点,感恩@amosbird提供了丝滑的ldb toolchain工具, 它给了Doris最友好的编译体验。

最后,也希望大家多多支持Apache Doris,多多给Doris贡献代码,感谢~~

5.参考链接

修复Pull Request
Ldb toolchain的修复版本

目录
相关文章
|
2月前
|
SQL 存储 JSON
阿里云数据库 SelectDB 内核 Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%
亲爱的社区小伙伴们,Apache Doris 2.1.0 版本已于 2024 年 3 月 8 日正式发布,新版本开箱盲测性能大幅优化,在复杂查询性能方面提升100%,新增Arrow Flight接口加速数据读取千倍,支持半结构化数据类型与分析函数。异步多表物化视图优化查询并助力仓库分层建模。引入自增列、自动分区等存储优化,提升实时写入效率。Workload Group 资源隔离强化及运行时监控功能升级,保障多负载场景下的稳定性。新版本已经上线,欢迎大家下载使用!
阿里云数据库 SelectDB 内核 Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%
|
2月前
|
SQL 监控 数据处理
实时计算 Flink版产品使用合集之开启 MiniBatch 优化会引入乱序问题如何解决
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
SQL 算法 关系型数据库
实时计算 Flink版产品使用合集之哪个版本支持使用不锁表功能
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用合集之mysql cdc支持全量的时候并发读取,该如何配置
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
消息中间件 关系型数据库 MySQL
实时计算 Flink版产品使用合集之2.2.1版本同步mysql数据写入doris2.0 ,同步完了之后增量的数据延迟能达到20分钟甚至一直不写入如何解决
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
消息中间件 SQL Oracle
实时计算 Flink版产品使用合集之增量同步速度较慢,导致延迟增加,该如何优化
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
Oracle 关系型数据库 MySQL
Flink CDC产品常见问题之用superset连接starrocks报错如何解决
Flink CDC(Change Data Capture)是一个基于Apache Flink的实时数据变更捕获库,用于实现数据库的实时同步和变更流的处理;在本汇总中,我们组织了关于Flink CDC产品在实践中用户经常提出的问题及其解答,目的是辅助用户更好地理解和应用这一技术,优化实时数据处理流程。
|
2月前
|
消息中间件 NoSQL Java
Flink CDC产品常见问题之文件增大如何解决
Flink CDC(Change Data Capture)是一个基于Apache Flink的实时数据变更捕获库,用于实现数据库的实时同步和变更流的处理;在本汇总中,我们组织了关于Flink CDC产品在实践中用户经常提出的问题及其解答,目的是辅助用户更好地理解和应用这一技术,优化实时数据处理流程。
|
2月前
|
SQL 存储 关系型数据库
Presto【实践 01】Presto查询性能优化(数据存储+SQL优化+无缝替换Hive表+注意事项)及9个实践问题分享
Presto【实践 01】Presto查询性能优化(数据存储+SQL优化+无缝替换Hive表+注意事项)及9个实践问题分享
278 0
|
9月前
|
运维 关系型数据库 MySQL
Doris 运维篇:Apache Doris tablet错误问题实操案例(一)
Doris 运维篇:Apache Doris tablet错误问题实操案例(一)
179 0