C ++ valarray与vector

我喜欢vector很多。 他们漂亮,快速。 但是我知道这个叫valarray的东西存在。 为什么我会用valarray代替vector? 我知道valarrays有一些语法糖,但除此之外,它们何时有用?

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

Valarrays也有类,允许你以合理简单的方式进行切片,尽pipe这部分标准可以使用更多的工作。 调整他们的大小是破坏性的,他们缺乏迭代器。

所以,如果是你正在使用的数字,而且便利性并不是那么重要的话,请使用valarrays。 否则,向量就更方便了。

valarray是一个在错误的时间出生在错误的地方的孤儿。 这是一个优化的尝试,特别是对于那些用于重型math的机器,特别是像Crays这样的vector处理器。

对于vector处理器,你通常想要做的是对整个数组应用一个单一的操作,然后将下一个操作应用到整个数组,等等,直到你完成了你需要做的一切。

除非你处理的是相当小的数组,然而,这对于caching来说往往效果不佳。 在大多数现代机器上,你通常会喜欢的(尽可能)是加载数组的一部分,做所有你要做的操作,然后移动到数组的下一部分。

valarray也可以消除任何混淆的可能性,至less在理论上它可以让编译器提高速度,因为它更容易将值存储在寄存器中。 但实际上,我并不确定任何真正的实现都会在很大程度上利用这个优势。 我怀疑这是一个问题 – 如果没有编译器的支持,它就不会stream行起来,只要它不受欢迎,就没有人会为编译器支持而烦恼。

也有令人眼花缭乱(字面意思)的辅助类的arrays与valarray一起使用。 你可以得到slice,slice_array,gslice和gslice_array来玩valarray的片断,并且像一个multidimensional array一样。 你也可以通过mask_array来“屏蔽”一个操作(例如,把x中的元素添加到y中,但只在z不为零的位置)。 为了使valarray的使用更为简单,你必须学习很多关于这些辅助类的东西,其中一些非常复杂,而且没有一个看起来(至less在我看来)是非常好的。

底线:虽然它有光彩的时刻,而且可以做得很整齐,但也有一些非常好的理由,它们是(而且几乎肯定会)保持晦涩的。

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

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

在C ++ 98的标准化过程中,valarray被devise为允许某种快速的math计算。 然而,在那个时候,Todd Veldhuizen发明了expression式模板并创build了blitz ++ ,并且发明了类似的模板元技术,这使得标准甚至在标准发布之前就已经过时了。 IIRC,valarray原来的提议者放弃了标准化的一半,如果真的的话也没有帮助。

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

但请记住,这一切都隐约记得传闻。 拿一点盐,并希望有人纠正或确认这一点。

valarray应该让一些FORTRANvector处理的好处在C ++上擦掉。 不知何故,必要的编译器支持从未真正发生。

Josuttis书籍包含一些关于valarray( 这里和这里 )的有趣的(有点贬低的)评论。

不过,Intel现在似乎正在重新审视valarray的最新编译器版本(例如,请参见幻灯片9 )。 这是一个有趣的发展,因为他们的4路SIMD SSE指令集即将join8路AVX和16路Larrabee指令,并且为了便于携带,它可能会更好地编码valarray比(说)内在。

我知道valarrays有一些语法糖

我不得不说,我不认为std::valarrays有许多语法糖。 语法是不同的,但我不会称之为“糖”的差异。 这个API很奇怪。 “ C ++编程语言”中有关std::valarray的部分提到了这个不寻常的API,以及由于std::valarray需要高度优化的事实,所以使用它们的任何错误消息都可能是非直观的。

出于好奇,大约在一年前,我将std::valarraystd::vector std::valarray 。 我不再有代码或精确的结果(尽pipe编写自己的代码不应该很难)。 使用GCC当使用std::valarray进行简单的math计算时,我得到了一些性能上的好处,但是对于我的实现来说,计算标准偏差(当然,标准偏差并不那么复杂,就math而言)。 我怀疑在一个大的std::vector中的每个项目上的操作对caching的操作比在std::valarray上操作std::valarray注意 ,根据musiphil的build议,我已经设法从vectorvalarray获得几乎相同的性能)。

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


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

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

对于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 lambda。

很明显,这两个代码段做了不同的事情。 首先, std::vector示例不会像std::valarray示例那样创build一个中间集合。 不过,我认为比较它们是公平的,因为差异与std::vectorstd::valarray之间的差异std::valarray

当我写这个答案时,我怀疑从两个std::valarraystd::valarray示例中的最后一行)中减去元素的值std::valarray std::valarray示例中的相应行更不易于caching -这恰好也是最后一行)。

事实certificate,但是,那

 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示例的function相同,性能几乎相同。 最后,问题是你喜欢哪个API。

C ++ 11标准说:

valarray数组类定义为不含某些forms的别名,因此允许对这些类的操作进行优化。

请参阅C ++ 11 26.6.1-2。

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

自C ++ 98以来,C ++标准库提供了用于处理数值数组的类valarray <>。 valarray是线性值序列的math概念的表示。 它有一个维度,但是可以通过计算索引和强大的子集能力的特殊技术来获得更高维度的错觉。 因此,valarray可以作为vector和matrix运算的基础,也可以用来处理性能良好的多项式方程的math系统。 valarray类可以进行一些棘手的优化,以获得处理值数组的良好性能。 然而,目前还不清楚C ++标准库的这个组件在未来有多重要,因为其他有趣的开发工作performance更好。 最有趣的例子之一是闪电战系统。 如果你对数字处理感兴趣,你应该看看它。 valarray类devise得不是很好。 事实上,没有人试图确定最终规范是否工作。 发生这种情况是因为没有人觉得这些课程“负责任”。 在第一个C ++标准完成之前,将valarrays引入C ++标准库的人离开了委员会。 例如,要使用valarrays,通常需要一些不方便和耗时的types转换。