如何替换源代码或库中的 __ieee754_exp_avx 调用?

Posted

技术标签:

【中文标题】如何替换源代码或库中的 __ieee754_exp_avx 调用?【英文标题】:How to replace __ieee754_exp_avx calls from source code or library? 【发布时间】:2017-01-18 15:16:34 【问题描述】:

来自 libm*.so 的 __ieee754_exp_avx 在某个源代码中被大量使用,我想用更快的 exp(x) 实现替换它吗?

自定义 exp(x):

inline
double exp2(double x) 
   x = 1.0 + x / 1024;
   x *= x; x *= x; x *= x; x *= x;
   x *= x; x *= x; x *= x; x *= x;
   x *= x; x *= x;
return x;

我应该使用哪些 gcc 标签来使 gcc 自动使用自定义的 exp(x) 实现?如果 gcc 不可能,那我该怎么做呢?

https://codingforspeed.com/using-faster-exponential-approximation/

【问题讨论】:

最好的办法是分析代码,然后手动放入 exp2 调用。 (pow() 也会调用 exp())。通常代码会因为失去准确性而崩溃,你需要小心地做这种事情。 【参考方案1】:

不要。此函数比exp 的本机实现要慢,并且是极差的近似值。

首先,速度。我的基准测试表明,根据您的编译器和 CPU,exp2 的这种实现可能比原生 exp 慢 1.5 倍到 4.5 倍。我不确定网站从哪里得到他们的数据——“比传统的 exp 快​​ 360 倍”似乎很荒谬,并且与我的测试完全不一致。

第二,准确性。 exp2(x)x ≤ 1exp(x) 相当接近,但对于较大的值则失败得很厉害。例如:

exp(1)   = 2.7182818
exp2(1)  = 2.7169557 (0.05% too low)

exp(2)   = 7.3890561
exp2(2)  = 7.3746572 (0.20% too low)

exp(5)   = 148.41316
exp2(5)  = 146.61829 (1.21% too low)

exp(10)  = 22026.466
exp2(10) = 20983.411 (4.74% too low)

exp(20)  = 4.851652e+08
exp2(20) = 4.0008755e+08 (17.5% too low)

虽然您获得此功能的网站声称“对于小于 5 的输入有很好的一致性”,但事实并非如此。 1.21% 的差异(对于x=5)是巨大的,并且可能会在使用此近似值的任何计算中导致重大错误。

【讨论】:

好点。我会注意到,OP 获得实现的文章的作者以“在中性网络中,数学函数 e^n 其中 n 通常很小(例如,小于 2),你可以避免math.h 提供的昂贵的exp()(对于其他编程语言,提供了类似的内置系统函数)”,因此它仅适用于小参数。我同意尽管速度要求是不合理的。递归自乘会产生大量的数据依赖关系,使其受到指令延迟的极大限制。 我几乎尝试实现它,我想我应该寻找更快的 exp(x) 实现。 @DominicGuana 不。您应该寻找一种方法来优化您的应用程序以减少对exp() 的调用次数。【参考方案2】:

别这样。该函数看起来比内置代码慢方式,而且在精度方面绝对不行。

如果您需要 SIMD(单指令,多数据)优化的 exp 功能,即。您不是在计算单个值,而是在计算一系列值,有 C 库可以为您执行此操作。我想强调VOLK,内核向量优化库,它是 DSP 密集型 GNU Radio 项目的衍生产品。

它实现了自己的expf(单精度求幂——如果你愿意接受错误,当然没有理由到处使用双精度浮点数);这是在我的机器上的比较:

RUN_VOLK_TESTS: volk_32f_expfast_32f(131071,1987)
a_avx completed in 60.119ms
a_sse4_1 completed in 62.052ms
u_avx completed in 60.376ms
u_sse4_1 completed in 62.131ms
generic completed in 2383.73ms

因此,对于 1987 年对 131071 个元素的向量的迭代,所有 SIMD 优化内核的速度都快了 40 倍——这相当不错,但与您引用的网站的大胆 360 倍声明相去甚远。

用到的expfast函数的源码可以在here找到。

在其核心,该实现依赖于浮点表示——这是一个非常好的主意。

它承认它有 7% 的错误边界——这差不多!

【讨论】:

嗨,我接受了另一个答案,但也赞成你展示更快的 exp(x) 实现,我想我会尝试根据 Cristi 的帖子来实现这个。【参考方案3】:

这更像是一种解决方法(gainarie):

exp2 定义放在一个.h 文件中:

//  exp2.h

#if !defined(__EXP2__H__)
#define __EXP2__H__

inline double exp2(double x) 
    x = 1.0 + x / 1024;
    x *= x; x *= x; x *= x; x *= x;
    x *= x; x *= x; x *= x; x *= x;
    x *= x; x *= x;
    return x;


#endif  //__EXP2__H__

现在,这个文件最终必须包含(无论是直接还是间接)在所有调用exp.c(xx)文件中——这可能是一个如果现有的代码库很大,工作会很痛苦。

然后,在编译代码的时候,将-D(预处理器定义)传递给gcc(不知道支持这种形式的最低版本;v5.4.0 确实)像这样:-D'exp(X)=exp2(X)'.

注意:您不再需要 libm.so.*(-lm) 在链接时(至少就exp 而言),所以你可以删除它。实际上,删除它是一个好主意(暂时 - 如果您正在使用其他数学函数,永久 - 否则),这样如果有任何 .c(xx) 文件不' t 包含 exp2.h,链接器将吐出与exp 相关的 undefined reference 错误(如果使用其他数学函数,在通过包含 exp2 解决所有这些错误后.h 在适当的 .c(xx) 文件中,您必须将其添加回来),否则您可能会在代码中混合使用 exp/exp2 调用。

【讨论】:

以上是关于如何替换源代码或库中的 __ieee754_exp_avx 调用?的主要内容,如果未能解决你的问题,请参考以下文章

IEEE-754 蟒蛇

Python中的浮点字符串到IEEE 754 binary128

Java虚拟机规范阅读IEEE754简介以及Java虚拟机中的浮点算法

QT实现IEEE754转换

如何解释尾数中的隐藏位? MIPS 代码 IEEE-754

从 IEEE 754-2008 十进制浮点数据转换为二进制浮点格式