Valarray 对矢量

我很喜欢矢量。它们又漂亮又快。但我知道有个叫 valarray 的东西存在。我为什么要用 valarray 而不是向量呢?我知道 valarray 有一些语法上的优势,但除此之外,它们什么时候有用呢?

63976 次浏览

Valarray (值数组)旨在为 C + + 带来一些 Fortran 的速度。您不会创建一个指针的 valarray,这样编译器就可以对代码进行假设并更好地优化它。(Fortran 速度如此之快的主要原因是没有指针类型,所以不会出现指针别名。)

Valarray 还有一些类,它们允许您以一种相当简单的方式将它们分割开来,尽管标准的这一部分可能需要更多的工作。调整它们的大小是破坏性的,而且 它们缺少迭代器从 C + + 11开始就有迭代器了。

所以,如果你使用的是数字,方便性并不那么重要,那么就使用 valarray。否则,向量就会方便得多。

在 C + + 98的标准化过程中,valarray 被设计成允许某种快速的数学计算。然而,大约在那个时候,Todd Veldhuizen 发明了表达式模板并创建了 闪电战,类似的模板元技术也被发明出来,这使得 valarray 在标准发布之前就已经过时了。IIRC,valarray 的最初提出者在标准化的过程中抛弃了它,这对它也没有任何帮助。

ISTR 认为,它没有从标准中删除的主要原因是,没有人花时间彻底评估这个问题,并写了一份建议书来删除它。

然而,请记住,这一切都是模糊记得的道听途说。对此持保留态度,希望有人能纠正或证实这一点。

在 C + + 中,valarray 可以让 FORTRAN 向量处理的优点得到发挥。不知何故,必要的编译器支持从未真正发生过。

Josuttis 的书包含了一些关于 valarray (给你给你)的有趣的(有些轻蔑的)评论。

然而,英特尔现在似乎在他们最近的编译器版本中重新审视 valarray (例如参见 幻灯片9) ; 这是一个有趣的发展,因为他们的4路 SIMD SSE 指令集即将被8路 AVX 和16路 Larrabee 指令加入,并且为了便于移植,使用像 valarray 这样的抽象编码可能比(比如)内部函数要好得多。

valarray就像一个在错误的时间出生在错误的地点的孤儿。这是一个优化的尝试,相当具体的机器,用于重型数学当它被写-特别是,向量处理器,如克雷斯。

对于一个并行向量处理机,你通常想要做的是对整个数组应用一个操作,然后对整个数组应用下一个操作,以此类推,直到你完成了你需要做的所有事情。

但是,除非您处理的是相当小的数组,否则缓存的效果往往很差。在大多数现代机器上,您通常更喜欢(尽可能地)加载数组的一部分,在它上面执行所有要执行的操作,然后转移到数组的下一部分。

valarray还被认为可以消除任何混淆的可能性,混淆(至少在理论上)可以让编译器提高速度,因为它可以更自由地将值存储在寄存器中。然而,在现实中,我根本不确定是否有任何真正的实现在很大程度上利用了这一点。我怀疑这是一个先有鸡还是先有蛋的问题——没有编译器的支持,它就不会流行起来,只要它不流行,就没有人会费力去编译它来支持它。

还有一个令人困惑的(字面意思)辅助类数组可用于 valarray。你可以让 sliceslice_arraygslicegslice_array来处理 valarray的片段,让它像一个多维数组一样工作。您还可以让 mask_array“屏蔽”一个操作(例如,在 x 到 y 中添加项,但只能在 z 为非零的位置)。要使用 valarray,您必须学习很多关于这些辅助类的知识,其中有些非常复杂,而且似乎没有一个(至少对我来说)有很好的文档说明。

底线: 虽然它有辉煌的时刻,可以做一些事情相当干净,也有一些非常好的理由,它是(几乎肯定仍然)模糊的。

编辑(八年后,2017年) : 前面的一些至少在某种程度上已经过时了。例如,Intel 已经为他们的编译器实现了一个优化版本的 valarray。它使用 Intel 集成性能原语(Intel IPP)来提高性能。虽然确切的性能改进毫无疑问是不同的,但是与使用 valarray的“标准”实现编译的相同代码相比,使用简单代码进行快速测试可以显示出大约2:1的速度改进。

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

我知道 valarray 有一些语法上的优势

我不得不说,我不认为 std::valarrays有太多的方式在句法糖。语法是不同的,但我不会把这种差别称为“糖”API 很奇怪。C++程式语言中关于 std::valarray的部分提到了这个不同寻常的 API,而且由于 std::valarray需要高度优化,因此在使用它们时得到的任何错误消息可能都是不直观的。

出于好奇,大约一年前,我让 std::valarray对抗 std::vector。我不再有代码或精确的结果(虽然它应该不难编写自己的)。当使用 std::valarray进行简单的数学计算时,使用 GCC i 是的可以获得一些性能优势,但是对于我的实现来说,计算标准差就不是那么复杂了(当然,就数学而言,标准差并没有那么复杂)。我怀疑在大型 ABC1中对每个项目的操作在缓存方面比在 std::valarray上的操作发挥得更好。(注意,遵循 std::vector0的建议,我已经设法从 vectorvalarray获得几乎相同的性能)。

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


std::vectorstd::valarray都将数据存储在一个连续的块中。但是,他们使用不同的模式访问数据,更重要的是,与 std::vector的 API 相比,std::valarray的 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::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的 lambdas。

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

当我写这个答案时,我怀疑从两个 std::valarray(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。

C + + 11标准规定:

Valarray 数组类定义为不包含某些形式的 别名,从而允许对这些类的操作进行优化。

见 C + + 1126.6.1-2。

我找到了 valarray 的一个好用法。 就像数字数组一样使用 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()

Valarray 适用于繁重的数字任务,比如计算流体力学或计算结构动力学,在这些任务中,数组包含数百万个项,有时是数千万个项,并且用数百万个时间步骤对它们进行循环迭代。也许今天的 std: : Vector 具有类似的性能,但是在大约15年前,如果您想编写一个高效的数值求解器,valarray 几乎是必需的。

使用 ABc0,你可以使用标准的数学符号,比如 ABc1。除非您定义自己的运算符,否则向量不可能做到这一点。