C ++ valarray与矢量


159

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


2
前几天也只是在考虑这一点。据我所知,它实际上只是专门的数学向量。
GManNickG

valarray不做表达式模板吗?
Mooing Duck 2013年

物理学家乌尔里希·穆特泽(Ulrich Mutze)为valarray 这里这里
生命平衡2014年

Answers:


70

Valarrays(值数组)旨在将Fortran的某些速度引入C ++。您不会创建指针的valarray,因此编译器可以对代码进行假设并更好地对其进行优化。(Fortran这么快的主要原因是没有指针类型,所以不能有指针别名。)

Valarrays还具有一些类,这些类使您可以以一种相当简单的方式对其进行切片,尽管该标准的这一部分可能需要更多的工作。调整它们的大小具有破坏性,并且缺少迭代器。

因此,如果使用的是数字,那么使用valarray并不是很重要。否则,矢量将更加方便。


11
它们并非旨在避免指针。C ++ 11在valarray中定义了begin()和end(),这些迭代器将迭代器返回给它们
Mohamed El-Nakib 2014年

3
@ user2023370:这就是为什么如此多的Fortran用户更喜欢Fortran 77的原因:)
Michael

152

valarray是一种在错误的时间在错误的地方出生的孤儿。这是一种优化的尝试,特别是针对编写时用于重型数学的机器-尤其是矢量处理器(如Crays)。

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

但是,除非您要处理的数组很小,否则缓存的效果往往很差。在大多数现代计算机上,通常希望(在可能的范围内)加载数组的一部分,对数组进行所有操作,然后再移至数组的下一部分。

valarray还应该消除混叠的可能性,这至少在理论上可以使编译器提高速度,因为它可以更自由地将值存储在寄存器中。但是,实际上,我完全不确定任何实际的实现都可以在很大程度上利用此优势。我怀疑这是一个鸡与蛋的问题-没有编译器支持,它就不会流行,而且只要它不流行,就不会有人麻烦他们的编译器来支持它。

还有一些令人困惑的(字面意义上的)辅助类数组与valarray一起使用。您将获得sliceslice_arraygslicegslice_array使用的各个部分valarray,并使它的作用类似于多维数组。您还可以mask_array“屏蔽”操作(例如,将x中的项添加到y,但仅在z为非零的位置)。要简单地使用valarray,您必须了解很多有关这些辅助类的知识,其中一些非常复杂,并且没有一个(至少在我看来)没有很好的文档记录。

最重要的是:尽管它有光彩的一刻,可以很好地完成某些事情,但也有一些很好的理由使它(几乎肯定会保留)晦涩难懂。

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

因此,尽管我并不完全相信C ++程序员将开始大量使用valarray,但至少在某些情况下它可以提高速度。


1
是否特别不允许在valarray中存储任意对象类型?
user541686

6
@Mehrdad:是的-[Numeric.Requirements]有一个(相当长的)限制列表。仅举几个例子,所有抽象类和异常均被禁止。它还要求等价于(例如)副本构造与默认构造序列和赋值之间的等效关系。
杰里·科芬

@JerryCoffin吓人的。我们保证不会使用它。
Hani Goc 2015年

4
我不会基于恐惧来决定。我会根据您是否需要存储使用其禁止功能的元素来决定。
杰里·科芬

3
@annoying_squid:如果您要添加更具体且(您认为)准确的信息,请随时添加显示该信息的答案。就目前而言,您的评论似乎并未添加任何有用的信息。
杰里·科芬

39

在C ++ 98的标准化过程中,将valarray设计为允许进行某种快速的数学计算。但是,在那时,Todd Veldhuizen发明了表达模板并创建了blitz ++,并且发明了类似的模板元技术,这使得valarray在标准发布之前就已经过时了。IIRC是valarray的原始提议者,已将其放弃到标准化的一半,(如果为真)也无济于事。

ISTR,未将其从标准中删除的主要原因是,没有人花时间对问题进行彻底评估并撰写提案以将其删除。

但是请记住,所有这些都是模糊的记忆传闻。随便吃一点盐,希望有人能纠正或确认。


表达式模板也可以同样归功于Vandevoorde,对吗?
Nikos Athanasiou 2014年

@Nikos:我不知道。我可能是错的。您对阅读有什么支持?
2014年

1
在“ C ++模板-完整指南”一书中提到过,我认为它们都是独立发明的,这一点已为人们普遍接受。
Nikos Athanasiou 2014年

27

我知道valarrays有一些语法糖

我不得不说,我认为std::valarrays语法糖的使用方式不多。语法不同,但是我不会称其为“糖”。该API很奇怪。《C ++编程语言》中关于std::valarrays 的部分提到了这种不寻常的API,并且由于希望对s进行高度优化,因此在使用它们时收到的任何错误消息可能都是非直觉的。std::valarray

出于好奇,大约一年前,我std::valarray反对了std::vector。我不再需要代码或精确的结果了(尽管编写自己的代码并不难)。使用GCC 进行简单数学运算时,确实获得了一点性能好处std::valarray,但是对于我的实现来计算标准偏差却没有(在数学上,标准偏差并不那么复杂)。 我怀疑在std::vector缓存上,对每个项目的操作都比对std::valarrays的操作更好。注意,根据musiphil的建议,我设法从vector和获得了几乎相同的性能valarray)。

最后,我决定在使用std::vector时要特别注意内存分配和临时对象创建之类的事情。


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

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

对于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::slice或更聪明std::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 lambda。

显然,这两个代码段做了不同的事情。首先,该std::vector示例不会像该std::valarray示例那样进行中间集合。但是,我认为比较它们是公平的,因为差异与std::vector和之间的差异有关std::valarray

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

但是事实证明,

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。


我想不出为什么a std::vector缓存比a更好玩的任何原因std::valarray;它们都为其元素分配单个连续的内存块。
musiphil

1
@musiphil我的回复对评论太久了,所以我更新了答案。
Max Lybbert

1
对于valarray上面的示例,您不必构造一个temp valarray对象,但是您可以完成此操作std::valarray<double> differences_from_mean = original_values - mean;,然后缓存行为应类似于该vector示例。(顺便说一句,如果mean真的是int,不是double,您可能需要static_cast<double>(mean)。)
musiphil

感谢您提出的清理垃圾桶的建议valarray。我需要查看是否可以提高性能。至于mean存在int:那是一个错误。我最初使用ints 编写了示例,然后意识到mean由于截断,因此与实际均值相差很远。但是我在第一轮编辑中错过了一些必要的更改。
Max Lybbert

@musiphil你是对的;这种变化使示例代码几乎达到了相同的性能。
Max Lybbert

23

valarray本来可以让FORTRAN向量处理优缺点在C ++上产生。某种程度上,必要的编译器支持从未真正发生过。

Josuttis的书中包含有关valarray的一些有趣(有些贬低)的评论(此处此处)。

但是,英特尔现在似乎在最近的编译器版本中重新审视valarray(例如,参见幻灯片9)。鉴于他们的4路SIMD SSE指令集即将与8路AVX和16路Larrabee指令结合在一起,因此这是一个有趣的发展,并且出于可移植性的考虑,使用像valarray比(说)内在函数要好。


16

我发现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);

在此处输入图片说明

我们可以用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()

2
当我在工作中发现valarray时,我的想法与您完全一样。我认为从现在开始,对于C ++中的数学处理问题,我将使用valarray,因为从数学角度看,代码看起来更容易理解。
Zachary Kraus

8

C ++ 11标准说:

valarray数组类被定义为没有某些混叠形式,因此可以优化对这些类的操作。

参见C ++ 11 26.6.1-2。


由于我假设标准确实定义了哪些形式,您可以引用它们吗?另外,这些是使用编码技巧来实现的,还是它们是基于编译器的针对语言其他位置的别名规则的例外?
underscore_d


0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.