【问题标题】:C trigonometry lookup table slower than math.h's functions?C三角查找表比math.h的函数慢?
【发布时间】:2021-07-09 16:25:08
【问题描述】:

我正在编写一个光线投射器,并试图通过为我最常用的三角函数(即sincostan)制作查找表来加速它。这第一个 sn-p 是我的表查找代码。为了避免为每个表创建一个查找表,我只创建一个sin 表,并将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 进行编译,没有启用任何优化,因此不会删除对每个 trig 函数的调用(我忽略了返回值)。

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);
}

【问题讨论】:

  • @CaspianAhlberg:寻找它每秒投射更多的光线,而不是占用少于 100% 的 cpu 时间。
  • 查找包含两个除法,在fmod 和除以步长。除法速度慢是出了名的。它包含几个可能导致次优的测试和分支指令的测试。 (高性能代码通常会减少控制流分支以支持数据操作。)我看不到问题中所述的步长。如果表很大,那么它不会保留在缓存中,内存查找会很慢。
  • 关于“我正在使用 clang 编译但未启用优化”:希望您的意思是编译时没有优化而不是查找例程。
  • @CaspianAhlberg:那么,系统sincos 比您的查找代码快的原因是因为我编写了它们。
  • @CaspianAhlberg 一般来说,如今在软件中使用查找表来实现数学函数很少是最佳选择。我自己在这方面的专业经验在很大程度上与本文中提出的观点相吻合:Marat Dukhan 和 Richard Vuduc,“基本函数的高通量计算方法”。 并行处理和应用数学,第 86-95 页。斯普林格,2014 年。

标签: c caching optimization trigonometry lookup-tables


【解决方案1】:

减少浮点数。

OP 的代码在应该是比例和查找中进行过多的 FP 数学运算。

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

查找表中的条目数应该是unsigned power-of-2,因此 mod 是一个简单的&amp;

首先,让我们简化并将 [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;
}

稍后我们可以使用四分之一大小的表 [0 ... pi/2] 并通过 integer 操作来引导选择/重建。

考虑到OP's 的低精度要求,考虑使用float 而不是double,包括float 函数,如lroundf()

【讨论】:

  • 比例因子是多少?
  • @CaspianAhlberg “有 [0 ... 2*pi) 映射到索引 [0 ... number_of_entries)” 所以scale_factor = number_of_entries/(2*pi)
  • @CaspianAhlberg 如果这有帮助,请稍后报告时间改进。
  • 我会做的。
猜你喜欢
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多