xtensor 的 "operator/" 比 numpy 的“/”慢

xtensor's "operator/" slower than numpy's "/"

我正在尝试将我以前用 python 编写的一些代码转移到 C++ 中,我目前正在测试 xtensor 以查看它是否可以比 numpy 更快地完成我需要的工作.

我的一个函数采用方阵 d 和标量 alpha,并执行逐元素运算 alpha/(alpha+d)。背景:这个函数是用来测试alpha的哪个值是'best',所以它处于一个循环中,d总是相同的,但是alpha是变化的。

以下所有时间尺度都是运行使用该函数的 100 个实例的平均值。

在numpy中,执行此操作大约需要0.27秒,代码如下:

def kfun(d,alpha):
    k = alpha /(d+alpha)
    return k

但 xtensor 大约需要 0.36 秒,代码如下所示:

xt::xtensor<double,2> xk(xt::xtensor<double,2> d, double alpha){
    return alpha/(alpha+d);
}

我也尝试过使用 std::vector 的以下版本,但我不想用很长时间 运行,即使只用了 0.22 秒。

std::vector<std::vector<double>> kloops(std::vector<std::vector<double>> d, double alpha, int d_size){
    for (int i = 0; i<d_size; i++){
        for (int j = 0; j<d_size; j++){
            d[i][j] = alpha/(alpha + d[i][j]);
        }
    }
    return d;
}

我注意到 xtensor 中的 operator/ 使用“惰性广播”,有没有办法让它立即生效?

编辑:

在Python中调用函数如下,使用time包定时

t0 = time.time()
for i in range(100):
    kk = k(dsquared,alpha_squared)
print(time.time()-t0)

在 C++ 中,我调用函数如下,并使用 chronos 计时:

//d is saved as a 1D npy file, an artefact from old code
auto sd2 = xt::load_npy<double>("/path/to/d.npy");

shape = {7084, 7084};
    xt::xtensor<double, 2> xd2(shape);
    for (int i = 0; i<7084;i++){
        for (int j=0; j<7084;j++){
            xd2(i,j) = (sd2(i*7084+j));
        }
    }

auto start = std::chrono::steady_clock::now();
for (int i = 0;i<10;i++){
    matrix<double> kk = kfun(xd2,4000*4000,7084);
}
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "k takes: " << elapsed_seconds.count() << "\n";

如果您希望 运行 此代码,我建议使用 xd2 作为对角线为零的对称 7084x7084 随机矩阵。

该函数的输出,一个名为 k 的矩阵,然后继续在其他函数中使用,但我仍然需要 d 保持不变,因为它将在以后重复使用。

结束编辑

对于 运行 我的 C++ 代码,我在终端中使用以下行:

cd "/path/to/src/" && g++ -mavx2 -ffast-math -DXTENSOR_USE_XSIMD -O3 ccode.cpp -o ccode -I/path/to/xtensorinclude && "/path/to/src/"ccode

提前致谢!

C++ 实现的一个问题可能是它创建了一个甚至可能是两个可以避免的临时副本。第一个副本来自不通过引用传递参数(或完美转发)。在不查看其余代码的情况下,很难判断这是否会对性能产生影响。如果保证在方法xk()之后不使用,编译器可能会将d移动到方法中,但更有可能将数据复制到d.

要通过引用传递,方法可以更改为

xt::xtensor<double,2> xk(const xt::xtensor<double,2>& d, double alpha){
    return alpha/(alpha+d);
}

要使用完美转发(并启用其他 xtensor 容器,如 xt::xarrayxt::xtensor_fixed),方法可以更改为

template<typename T>
xt::xtensor<double,2> xk(T&& d, double alpha){
    return alpha/(alpha+d);
}

此外,您可以避免为 return 值保留内存。同样,如果不看其余代码就很难判断。但是,如果该方法在循环内使用,并且 return 值始终具有相同的形状,那么在循环外创建 return 值并通过引用创建 return 可能是有益的.为此,可以将方法更改为:

template<typename T, typename U>
void xk(T& r, U&& d, double alpha){
    r = alpha/(alpha+d);
}

如果保证dr不指向同一个内存,可以进一步将r包裹在xt::noalias()中,避免之前的临时复制分配结果。对于函数的 return 值也是如此,以防您不通过引用 return。

祝你好运,编码愉快!