C 三角函数查找 table 比 math.h 的函数慢?

C trigonometry lookup table slower than math.h's functions?

我正在写一个 raycaster,我试图通过查找 tables 来加快它的速度,这些函数是我最常调用的三角函数,即 sincostan。第一个片段是我的 table 查找代码。为了避免为每个查找 table,我只查找一个 sin table,并将 cos(x) 定义为 sin(half_pi - x)tan(x)作为 sin(x) / cos(x).

#include <math.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

const float two_pi = M_PI * 2, half_pi = M_PI / 2;

typedef struct {
    int fn_type, num_vals;
    double* vals, step;
} TrigTable;

static TrigTable sin_table;

TrigTable init_trig_table(const int fn_type, const int num_vals) {
    double (*trig_fn) (double), period;
    switch (fn_type) {
        case 0: trig_fn = sin, period = two_pi; break;
        case 1: trig_fn = cos, period = two_pi; break;
        case 2: trig_fn = tan, period = M_PI; break;
    }

    TrigTable table = {fn_type, num_vals,
        calloc(num_vals, sizeof(double)), period / num_vals};

    for (double x = 0; x < period; x += table.step)
        table.vals[(int) round(x / table.step)] = trig_fn(x);

    return table;
}

double _lookup(const TrigTable table, const double x) {
    return table.vals[(int) round(x / table.step)];
}

double lookup_sin(double x) {
    const double orig_x = x;
    if (x < 0) x = -x;
    if (x > two_pi) x = fmod(x, two_pi);

    const double result = _lookup(sin_table, x);
    return orig_x < 0 ? -result : result;
}

double lookup_cos(double x) {
    return lookup_sin(half_pi - x);
}

double lookup_tan(double x) {
    return lookup_sin(x) / lookup_cos(x);
}

以下是我如何对我的代码进行基准测试:我的当前时间(以毫秒为单位)的函数来自 here。问题出现在这里:当计时我的 lookup_sinmath.hsin 时,我的变体需要大约三倍的时间:Table time vs default: 328 ms, 108 ms.

cos 的时间安排如下: Table time vs default: 332 ms, 109 ms

tan 的时间安排如下: Table time vs default: 715 ms, 153 ms

是什么让我的代码变慢了这么多?我认为预先计算 sin 值会大大加速我的代码。也许是 lookup_sin 函数中的 fmod ?请提供您的任何见解。我在没有启用优化的情况下使用 clang 进行编译,因此不会删除对每个触发函数的调用(我忽略了 return 值)。

const int64_t millis() {
    struct timespec now;
    timespec_get(&now, TIME_UTC);
    return ((int64_t) now.tv_sec) * 1000 + ((int64_t) now.tv_nsec) / 1000000;
}

const int64_t benchmark(double (*trig_fn) (double)) {
    const int64_t before = millis();

    for (double i = 0; i < 10000; i += 0.001)
        trig_fn(i);

    return millis() - before;
}

int main() {
    sin_table = init_trig_table(0, 15000);

    const int64_t table_time = benchmark(lookup_sin), default_time = benchmark(sin);
    printf("Table time vs default: %lld ms, %lld ms\n", table_time, default_time);

    free(sin_table.vals);
}

减少浮点运算。

OP 的代码在应该是缩放和查找的地方进行过多的 FP 数学运算。

将每个预先计算的因子的弧度缩放到索引中。

查找中的条目数 table 应该是 unsigned 的 2 次方,所以 mod 是一个简单的&.

首先,让我们简化并让 [0 ... 2*pi) 映射到索引 [0 ... number_of_entries) 来演示这个想法。

double lookup_sin_alt(double x) {
  long scaled_x = lround(x * scale_factor);  // This should be the _only_ line of FP code
  // All following code is integer code.
  scaled_x += number_of_entries/4 ; // If we are doing cosine
  unsigned index = scaled_x & (number_of_entries - 1);  // This & replaces fmod
  double result = table.vals[index];
  return result;
}

稍后我们可以使用四分之一大小 table [0 ... pi/2] 并使用 整数 操作引导 selection/reconstruction。

鉴于 OP's 低精度要求,请考虑在整个过程中使用 float 而不是 double,包括 float 函数,例如 lroundf().