-1

我知道标题令人困惑,但我不知道如何更好地描述它,让代码自己解释一下:

我有一个第三方库将复杂的标量定义为

typedef struct {
    float real;
    float imag;
} cpx;

所以复杂的数组/向量就像

cpx array[10];
for (int i = 0; i < 10; i++)
{
    /* array[i].real and array[i].imag is real/imag part of i-th member */
}

目前的情况是,在一个函数中,我有两个浮点数组作为参数,我使用两个临时局部复杂数组,例如:

void my_func(float *x, float *y) /* x is input, y is output, length is fixed, say 10 */
{
    cpx tmp_cpx_A[10]; /* two local cpx array */
    cpx tmp_cpx_B[10];
    
    for (int i = 0; i < 10; i++) /* tmp_cpx_A is based on input x */
    {
        tmp_cpx_A[i].real = do_some_calculation(x[i]);
        tmp_cpx_A[i].imag = do_some_other_calculation(x[i]);
    }

    some_library_function(tmp_cpx_A, tmp_cpx_B); /* tmp_cpx_B is based on tmp_cpx_A, out-of-place */
    
    for (int i = 0; i < 10; i++) /* output y is based on tmp_cpx_B */
    {
        y[i] = do_final_calculation(tmp_cpx_B[i].real, tmp_cpx_B[i].imag);
    }
}

我注意到在第一个循环之后x没有用,第二个循环就位。如果我可以使用与和tmp_cpx_B相同的内存进行构建,我可以节省一半的中间内存使用量。xy

如果复数数组定义为

typedef struct{
    float *real;
    float *imag;
} cpx_alt;

那么我可以简单地

cpx_alt tmp_cpx_B; 
tmp_cpx_B.real = x; 
tmp_cpx_B.imag = y;

做剩下的,但事实并非如此。

我不能更改第三个库复杂结构的定义,也不能cpx作为输入,因为我想向外部用户隐藏内部库而不破坏 API。

所以我想知道是否可以使用标量成员初始化结构数组,就像cpx标量数组x一样y

编辑1:对于一些常见的问题:

  1. 实际上,数组长度最多为 960,这意味着一个tmp_cpx数组将占用 7680 个字节。我的平台总共有 56k RAM,节省一个tmp_cpx将节省约 14% 的内存使用量。
  2. 第 3 方库是 KissFFt 并在复数数组上进行 FFT,它定义了自己的kiss_fft_cpx而不是标准的 <complex.h> 因为它可以使用 marco 在浮点/定点计算之间切换
4

2 回答 2

1

如果您想要符合标准的代码,则不能重用指向的内存xy保存与/数组cpx具有相同维度的数组。这种方法有几个问题。数组的大小加上数组的大小可能不等于数组的大小。和数组可能不在连续内存中。C 标准不保证指针类型双关语可以正常工作。xyxycpxxy

所以简短的回答是:不,你不能

但是,如果您愿意接受不是 100% 符合标准的代码,则很有可能可以做到。您必须在您的特定系统上非常仔细地检查它并接受您不能将代码移动到另一个系统而不再次在该系统上非常仔细地检查它(注意:系统我的意思是 cpu、编译器和它的版本等等上)。

有些事情你需要确保

  1. x和数组在内存中y是连续的

  2. cpx数组与其他两个数组的大小相同。

  3. 这个对齐没问题

如果这是真的,您可以使用非标准类型的双关语。喜欢:

#define SIZE 10

// Put x and y into a struct    
typedef struct {
    float x[SIZE];
    float y[SIZE];
} xy_t;

添加一些断言以检查内存布局是否没有任何填充。

assert(sizeof(xy_t) == 2 * SIZE * sizeof(float));
assert(sizeof(cpx) == 2 * sizeof(float));
assert(sizeof(cpx[SIZE]) == sizeof(xy_t));
assert(alignof(cpx[SIZE]) == alignof(xy_t));

my_func变化中

cpx tmp_cpx_A[SIZE];
cpx tmp_cpx_B[SIZE];

cpx tmp_cpx_A[SIZE];
cpx* tmp_cpx_B = (cpx*)x;  // Ugly, non-portable type punning 

这是“危险”的部分。不是定义一个新数组,而是通过指针转换使用类型双关语,以便指向与(and )tmp_cpx_B相同的内存。这符合标准,但在大多数系统上,当上述断言成立时,它可能会起作用。xy

现在像这样调用函数:

xy_t xt;
for (int i = 0; i < SIZE; i++)
{
    xt.x[i] = i;
}
my_func(xt.x, xt.y);

尾注正如多次指出的那样,这种方法不符合标准。所以你应该只做这种事情,如果你真的,真的需要减少你的内存使用。您需要检查您的特定系统以确保它可以在您的系统上运行。

于 2021-10-18T08:53:30.373 回答
0

首先,请注意 C 有一个用于复数的标准化库,<complex.h>. 您可能想使用那个而不是一些非标准的 3rd 方库。


您的代码的主要问题可能是执行速度,而不是内存使用。在大多数系统上分配2 * 10 * 2 = 40浮点数并不是什么大问题。另一方面,你一遍又一遍地触摸同一个内存区域。这可能是不必要的低效。

考虑这样的事情:

void my_func (size_t size, const float x[size], float y[size])
{
  for(size_t i=0; i<size; i++)
  {
    cpx cpx_A = 
    {
      .real = do_some_calculation(x[i]),
      .imag = do_some_other_calculation(x[i])
    };
    cpx cpx_B;

    // ensure that the following functions work on single variables, not arrays:
    some_library_function(&cpx_A, &cpx_B);
    y[i] = do_final_calculation(cpx_B.real, cpx_B.imag); 
  }
}

更少的指令和更少的分支。作为奖励,更少的堆栈使用。


从理论上讲,您还可以通过restrict限定参数来获得一些 CPU 周期,尽管当我在此代码 (gcc x86-64) 上尝试时没有发现任何改进。

于 2021-10-18T06:54:02.453 回答