C ++ valarray与矢量

时间:2009-10-21 17:53:31

标签: c++ stl stdvector c++-standard-library valarray

我非常喜欢矢量。他们很快,很快。但我知道这个叫做valarray的东西存在。为什么我会使用valarray而不是矢量?我知道valarray有一些语法糖,但除此之外,它们什么时候有用呢?

9 个答案:

答案 0 :(得分:140)

valarray是一种在错误的时间出生在错误地方的孤儿。这是一次优化尝试,非常适合用于编写重型数学的机器 - 特别是像Crays这样的矢量处理器。

对于矢量处理器,您通常要做的是对整个阵列应用单个操作,然后将下一个操作应用于整个阵列,依此类推,直到您完成了所需的一切。

然而,除非你处理的是相当小的数组,否则缓存效果会很差。在大多数现代机器上,你通常喜欢的(尽可能的)是加载数组的一部分,对它进行所有操作,然后转到数组的下一部分。 / p>

valarray也应该消除任何混叠的可能性,这至少在理论上会让编译器提高速度,因为它可以更自由地将值存储在寄存器中。但实际上,我并不确定任何真正的实现都会在很大程度上利用这一点。我怀疑这是一个鸡蛋和鸡蛋的问题 - 没有编译器支持它没有变得流行,只要它不受欢迎,没有人会麻烦他们的编译器支持它。< / p>

还有一个与valarray一起使用的令人眼花缭乱的(字面上)辅助类数组。你得到slice,slice_array,gslice和gslice_array来玩valarray的片段,并让它像一个多维数组。您还可以使用mask_array“屏蔽”操作(例如,将x中的项添加到y,但仅限于z非零的位置)。为了更多地使用valarray,你必须学习很多关于这些辅助类的知识,其中一些非常复杂,而且似乎(至少对我来说)没有一个很好的记录。

底线:虽然它有一些光彩,但可以做得非常整齐,但也有一些非常好的理由(并且几乎肯定会保留)模糊不清。

编辑(八年后,2017年):前面的一些已经至少在某种程度上已经过时了。例如,英特尔为其编译器实现了valarray的优化版本。它使用英特尔集成性能基元(英特尔IPP)来提高性能。虽然确切的性能提升无疑会有所不同,但与使用valarray的“标准”实现编译的相同代码相比,使用简单代码进行的快速测试显示速度提高了2:1。

所以,虽然我并不完全相信C ++程序员会开始大量使用valarray,但在某些情况下它可以提供速度提升。

答案 1 :(得分:65)

Valarrays(值数组)旨在将Fortran的一些速度带入C ++。你不会做一个指针的变量,所以编译器可以对代码做出假设并更好地优化它。 (Fortran如此之快的主要原因是没有指针类型,因此没有指针别名。)

Valarrays也有类允许您以一种相当简单的方式对它们进行切片,尽管该部分标准可以使用更多的工作。调整它们的大小是破坏性的,它们缺乏迭代器。

所以,如果它是你正在使用的数字,并且方便并不是那么重要的使用valarrays。否则,向量会更方便。

答案 2 :(得分:37)

在C ++ 98的标准化过程中,valarray被设计为允许某种快速的数学计算。然而,在那个时候,Todd Veldhuizen发明了表达模板并创建了blitz++,并且发明了类似的模板元技术,这使得valarray在标准发布之前几乎已经过时了。 IIRC,valarray的原始提议者放弃了标准化的一半,(如果是真的)也没有帮助它。

ISTR认为没有从标准中删除的主要原因是没有人花时间彻底评估问题,并提出删除建议。

但是请记住,所有这些都是模糊地记住传闻。带着一点点盐,并希望有人纠正或证实这一点。

答案 3 :(得分:26)

  

我知道valarray有一些语法糖

我不得不说我不认为std::valarrays在语法糖方面有很多。语法不同,但我不会把差异称为“糖”。 API很奇怪。 C ++编程语言中关于std::valarray的部分提到了这个不寻常的API以及由于std::valarray预计会被高度优化,因此您获得的任何错误消息使用它们可能不直观。

出于好奇,大约一年前,我std::valarraystd::vector进站了。我不再拥有代码或精确的结果(尽管编写自己的代码并不难)。使用GCC I 在使用std::valarray进行简单数学运算时获得了一点性能优势,但不是我的实现计算标准偏差(当然,标准偏差并不复杂,就数学而言)。 我怀疑大std::vector中每个项目的操作对缓存的效果要好于std::valarray上的操作。注意,遵循来自{的建议{3}},我设法从vectorvalarray获得了几乎相同的效果。

最后,我决定使用std::vector,同时密切关注内存分配和临时对象创建等事项。


std::vectorstd::valarray都将数据存储在一个连续的块中。但是,他们使用不同的模式访问这些数据,更重要的是,std::valarray的API鼓励使用与std::vector的API不同的访问模式。

对于标准偏差示例,在特定步骤中,我需要找到集合的均值以及每个元素的值与均值之间的差异。

对于std::valarray,我做了类似的事情:

std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> temp(mean, original_values.size());
std::valarray<double> differences_from_mean = original_values - temp;

std::slicestd::gslice我可能更聪明。现在已经五年了。

对于std::vector,我做了类似的事情:

std::vector<double> original_values = ... // obviously, I put something here
double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0) / original_values.size();

std::vector<double> differences_from_mean;
differences_from_mean.reserve(original_values.size());
std::transform(original_values.begin(), original_values.end(), std::back_inserter(differences_from_mean), std::bind1st(std::minus<double>(), mean));

今天我肯定会以不同的方式写出来。如果不出意外,我会利用C ++ 11 lambdas。

很明显,这两个代码片段做了不同的事情。例如,std::vector示例不像std::valarray示例那样生成中间集合。但是,我认为比较它们是公平的,因为差异与std::vectorstd::valarray之间的差异有关。

当我写这个答案时,我怀疑从两个std::valarray中减去元素的值(std::valarray示例中的最后一行)将比std::vector中的相应行更少缓存友好。 {1}}示例(恰好也是最后一行)。

但事实证明

std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> differences_from_mean = original_values - mean;

std::vector示例相同,并且具有几乎相同的性能。最后,问题是您更喜欢哪种API。

答案 4 :(得分:23)

valarray应该让一些FORTRAN矢量处理的好处在C ++上消失。不知何故,必要的编译器支持从未发生过。

Josuttis书中包含了一些关于valarray(herehere)的有趣(有点贬低)的评论。

然而,英特尔现在似乎正在重新审视valarray在他们最近的编译器版本中(例如见slide 9);这是一个有趣的发展,因为他们的4路SIMD SSE指令集即将加入8路AVX和16路Larrabee指令,为了便于携带,用类似抽象编码可能会好得多。 valarray比(比如)内在论。

答案 5 :(得分:13)

我找到了valarray的一个很好的用法。 它就像numpy数组一样使用valarray。

auto x = linspace(0, 2 * 3.14, 100);
plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f);

enter image description here

我们可以使用valarray实现上述。

valarray<float> linspace(float start, float stop, int size)
{
    valarray<float> v(size);
    for(int i=0; i<size; i++) v[i] = start + i * (stop-start)/size;
    return v;
}

std::valarray<float> arange(float start, float step, float stop)
{
    int size = (stop - start) / step;
    valarray<float> v(size);
    for(int i=0; i<size; i++) v[i] = start + step * i;
    return v;
}

string psstm(string command)
{//return system call output as string
    string s;
    char tmp[1000];
    FILE* f = popen(command.c_str(), "r");
    while(fgets(tmp, sizeof(tmp), f)) s += tmp;
    pclose(f);
    return s;
}

string plot(const valarray<float>& x, const valarray<float>& y)
{
    int sz = x.size();
    assert(sz == y.size());
    int bytes = sz * sizeof(float) * 2;
    const char* name = "plot1";
    int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, bytes);
    float* ptr = (float*)mmap(0, bytes, PROT_WRITE, MAP_SHARED, shm_fd, 0);
    for(int i=0; i<sz; i++) {
        *ptr++ = x[i];
        *ptr++ = y[i];
    }

    string command = "python plot.py ";
    string s = psstm(command + to_string(sz));
    shm_unlink(name);
    return s;
}

另外,我们需要python脚本。

import sys, posix_ipc, os, struct
import matplotlib.pyplot as plt

sz = int(sys.argv[1])
f = posix_ipc.SharedMemory("plot1")
x = [0] * sz
y = [0] * sz
for i in range(sz):
    x[i], y[i] = struct.unpack('ff', os.read(f.fd, 8))
os.close(f.fd)
plt.plot(x, y)
plt.show()

答案 6 :(得分:7)

C ++ 11标准说:

  

valarray数组类被定义为没有某些形式的   别名,从而允许对这些类的操作进行优化。

参见C ++ 11 26.6.1-2。

答案 7 :(得分:2)

借助std::valarray,您可以直接使用v1 = a*v2 + v3之类的标准数学符号。除非定义了自己的运算符,否则使用向量是不可能的。

答案 8 :(得分:0)

std :: valarray适用于繁重的数字任务,例如计算流体动力学或计算结构动力学,在这些任务中,您拥有数百万个数组,有时还有数千万个项目,并且您还用数百万个循环遍历它们时间步长。也许今天std :: vector具有可比的性能,但是大约15年前,如果要编写高效的数值求解器,则valarray几乎是强制性的。