如何解决提高具有等距输入的 2D 直接查找表的性能嵌入式 C
我在使用具有等距输入的直接查找表进行 2D 插值时缺乏性能。
目标是找到表 z(x,y) 中的 4 个值 (z00,z01,z11,z10) 分别对应输入 x 和 y 的两个最接近的值,(x0
例如以下查找:
x
y | 1 | 2 | 3 |
---|---|---|---|
4 | 20 | 23 | 29 |
6 | 35 | 37 | 43 |
8 | 47 | 50 | 55 |
由以下数组表示:
const f32 lookup {20,35,47,23,37,50,29,43,55}
此外,连同数组的定义,一个结构提供以下数据以允许更有效的查找:
lowest_x = 1;
lowest_y = 4;
step_x = 1;
step_y = 2;
length_x = 3;
length_y = 3;
该算法最耗时的部分是找到与输入 x 和 y 前后值的交集对应的索引。
我目前的方法是按如下方式计算它们:
鉴于 x0 和 y0 是指数的倍数:
index_x0 = u32 ((x-lowest_x)/step_x);
index_y0 = u32 ((y-lowest_y)/step_y);
那么 x0,x1,y0 和 y1 是:
x0 = lowest_x + index_x0 * step_x ;
x1 = x0 + step_x ;
y0 = lowest_y + index_y0 * step_y ;
y1 = y0 + step_y ;
查找 z(x,y) 的 4 个必要值是:
z00_index = index_x0*length_y0+index_y0;
z00= lookup[z00_index]
z01= lookup[z00_index+1]
z10= lookup[z00_index+length_y0];
z11= lookup[z00_index+length_y0+1];
然后将二维插值作为 x 沿 y0 和 y1 的两次插值和 y 的一次插值执行:
zxy0 = (x1-x)/(x1-x0)*z00 + (x-x0)/(x1-x0)*z10;
zxy1 = (x1-x)/(x1-x0)*z01 + (x-x0)/(x1-x0)*z11;
z = (y1-y)/(y1-y0)*zxy0 + (y-y0)/(y-y0)*zxy1;
关于如何改进这一点有什么建议吗?
解决方法
我不研究嵌入式,但有一些微优化的机会,特别是减少乘法和除法的次数,并避免再次重新计算相同的东西。
如果你先把指数计算成一个实数:
double ix = (x - x.base) / x.step;
您将获得一个“真实索引”,您可以将其转换为整数:
unsigned ix0 = ix;
现在,它们之间的区别为您提供了插值索引:
double r1 = ix - ix0; // == (x - x0) / (x1 - x0)
double r0 = 1.0 - r1; // == (x1 - x) / (x1 - x0)
您现在拥有 x
所需的一切:查找索引和插值系数。您不需要的是间隔开始和结束时的实际 x
值。对 y
执行相同操作以获得 iy0
、r0
和 r1
,您的插值是:
double z = r0 * (s0 * z00 + s1 * z01) + r1 * (s0 * z10 + s1 * z11);
这是每个坐标的一个除法,您可以通过预先计算因子 1.0 / x.step
和 1.0 / y.step
将其转化为乘法。
这是原始代码的完整示例实现和建议的改进。在 PC 上测试,我获得了 30%-40% 的加速,但没有在嵌入式上测试。小心:代码不做任何边界检查!
#include <stdlib.h>
#include <stdio.h>
typedef struct Lookup Lookup;
typedef struct Range Range;
struct Range {
double base;
double step;
unsigned length;
};
struct Lookup {
Range x,y;
double *data;
double xdenom;
double ydenom;
};
double lookup1(const Lookup *lo,double x,double y)
{
unsigned index_x0 = (x - lo->x.base) / lo->x.step;
unsigned index_y0 = (y - lo->y.base) / lo->y.step;
double x0 = lo->x.base + index_x0 * lo->x.step;
double x1 = x0 + lo->x.step;
double y0 = lo->y.base + index_y0 * lo->y.step;
double y1 = y0 + lo->y.step;
double *p = lo->data + index_x0 * lo->y.length + index_y0;
double z00 = p[0];
double z01 = p[1];
double z10 = p[lo->y.length];
double z11 = p[lo->y.length + 1];
double zxy0 = (x1 - x) / (x1 - x0)*z00 + (x - x0) / (x1 - x0)*z10;
double zxy1 = (x1 - x) / (x1 - x0)*z01 + (x - x0) / (x1 - x0)*z11;
double z = (y1 - y) / (y1 - y0)*zxy0 + (y - y0) / (y1 - y0)*zxy1;
return z;
}
double lookup2(const Lookup *lo,double y)
{
double ix = (x - lo->x.base) * lo->xdenom;
double iy = (y - lo->y.base) * lo->ydenom;
unsigned ix0 = ix;
unsigned iy0 = iy;
double r1 = ix - ix0;
double r0 = 1.0 - r1;
double s1 = iy - iy0;
double s0 = 1.0 - s1;
double *p = lo->data + (ix0 * lo->y.length + iy0);
double z00 = p[0];
double z01 = p[1];
double z10 = p[lo->y.length];
double z11 = p[lo->y.length + 1];
double z = r0 * (s0 * z00 + s1 * z01)
+ r1 * (s0 * z10 + s1 * z11);
return z;
}
double urand(void)
{
return rand() / (1.0 + RAND_MAX);
}
enum {
L = 1 << 24
};
int main(void)
{
Lookup lo = {
{10,0.1,80},{10,};
unsigned i,j;
double *p;
unsigned l = L;
double sum = 0;
lo.xdenom = 1.0 / lo.x.step;
lo.ydenom = 1.0 / lo.y.step;
p = lo.data = malloc(lo.x.length * lo.y.length * sizeof(*lo.data));
for (i = 0; i < lo.x.length; i++) {
for (j = 0; j < lo.y.length; j++) {
*p++ = urand();
}
}
while (l--) {
double x = lo.x.base + (lo.x.length * lo.x.step) * urand();
double y = lo.y.base + (lo.y.length * lo.y.step) * urand();
double z = lookup2(&lo,x,y);
sum += z;
}
printf("%g\n",sum / L);
free(lo.data);
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。