我非常喜欢矢量。它们既漂亮又快速。但我知道存在这个叫做 valarray 的东西。为什么我要使用 valarray 而不是向量?我知道 valarrays 有一些语法糖,但除此之外,它们什么时候有用?
9 回答
valarray
是一种在错误的时间出生在错误的地方的孤儿。这是一种优化的尝试,特别是针对在编写时用于重型数学的机器 - 特别是像 Crays 这样的矢量处理器。
对于矢量处理器,您通常想要做的是将单个操作应用于整个数组,然后将下一个操作应用于整个数组,依此类推,直到您完成了您需要做的所有事情。
但是,除非您处理的是相当小的数组,否则缓存往往效果不佳。在大多数现代机器上,您通常更喜欢(在可能的范围内)加载阵列的一部分,在其上执行您要执行的所有操作,然后继续进行阵列的下一部分。
valarray
还应该消除任何混叠的可能性,这(至少在理论上)可以让编译器提高速度,因为它可以更自由地将值存储在寄存器中。然而,实际上,我完全不确定任何真正的实现是否在很大程度上利用了这一点。我怀疑这是一个先有鸡还是先有蛋的问题——没有编译器支持,它就不会流行起来,只要它不流行,没有人会费心在他们的编译器上工作来支持它。
还有一个令人眼花缭乱的(字面意思)辅助类数组与 valarray 一起使用。你得到slice
,slice_array
和来玩 a 的片段,gslice
让它像一个多维数组一样工作。您还可以“屏蔽”一个操作(例如,将 x 中的项目添加到 y,但仅限于 z 非零的位置)。为了不只是简单地使用,您必须了解很多关于这些辅助类的知识,其中一些非常复杂,而且似乎(至少对我而言)没有一个有很好的文档记录。gslice_array
valarray
mask_array
valarray
底线:虽然它有辉煌的时刻,并且可以很好地完成一些事情,但也有一些很好的理由表明它(并且几乎肯定会保持)晦涩难懂。
编辑(八年后,即 2017 年):前面的一些内容至少在某种程度上已经过时了。例如,英特尔为他们的编译器实现了 valarray 的优化版本。它使用英特尔集成性能基元 (英特尔 IPP) 来提高性能。尽管确切的性能改进无疑各不相同,但与使用“标准”实现编译的相同代码相比,使用简单代码的快速测试显示速度提高了大约 2:1 valarray
。
因此,虽然我不完全相信 C++ 程序员将开始大量使用 C++ valarray
,但至少在某些情况下它可以提高速度。
Valarrays(值数组)旨在将 Fortran 的一些速度带入 C++。您不会制作指针的 valarray,因此编译器可以对代码做出假设并更好地优化它。(Fortran 这么快的主要原因是没有指针类型,所以不能有指针别名。)
Valarrays 也有允许你以相当简单的方式将它们分割的类,尽管标准的那部分可能需要更多的工作。调整它们的大小是破坏性的,它们缺乏迭代器,自 C++11 以来就有迭代器。
因此,如果您使用的是数字并且方便性并不是那么重要,请使用 valarrays。否则,向量会更方便。
在 C++98 的标准化过程中,valarray 旨在允许某种快速的数学计算。然而,大约在那个时候,Todd Veldhuizen 发明了表达式模板并创建了blitz++,并且发明了类似的模板元技术,这使得 valarrays 在标准发布之前就已经过时了。IIRC,valarray 的最初提议者在标准化的中途放弃了它,这(如果是真的)也没有帮助。
ISTR 认为它没有从标准中删除的主要原因是没有人花时间彻底评估问题并编写删除它的提案。
但是请记住,这一切都是依稀记得的道听途说。对此持保留态度,希望有人纠正或证实这一点。
我知道 valarrays 有一些语法糖
我不得不说我不认为std::valarrays
有太多的语法糖。语法不同,但我不会将差异称为“糖”。API 很奇怪。The C++ Programming Language中关于std::valarray
s的部分提到了这个不寻常的 API,并且由于s 预计会被高度优化,因此您在使用它们时收到的任何错误消息都可能是不直观的。std::valarray
出于好奇,大约一年前,我std::valarray
与std::vector
. 我不再拥有代码或精确的结果(尽管编写自己的代码应该不难)。使用 GCC 时,我在用于简单数学时确实获得了一点性能优势std::valarray
,但对于我的实现来计算标准偏差却没有(当然,就数学而言,标准偏差并不那么复杂)。 我怀疑在大型(注意,根据musiphil的建议,我已经设法从std::vector
缓存中对每个项目的操作比对std::valarray
s 的操作更好。vector
和获得几乎相同的性能valarray
)。
最后,我决定使用std::vector
,同时密切关注内存分配和临时对象创建等事情。
两者都std::vector
将std::valarray
数据存储在一个连续的块中。但是,他们使用不同的模式访问这些数据,更重要的是,API forstd::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
or 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::vector
和之间的差异有关std::valarray
。
当我写这个答案时,我怀疑从两个std::valarray
s (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。
我发现了 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++11 标准说:
valarray 数组类被定义为没有某些形式的别名,因此允许对这些类的操作进行优化。
参见 C++11 26.6.1-2。
std::valarray
您可以使用开箱即用的标准数学符号v1 = a*v2 + v3
。除非您定义自己的运算符,否则这对向量是不可能的。
std::valarray 适用于繁重的数值任务,例如计算流体动力学或计算结构动力学,其中您拥有包含数百万、有时是数千万项的数组,并且您在循环中以数百万个时间步长对它们进行迭代。也许今天 std::vector 具有相当的性能,但大约 15 年前,如果你想编写一个高效的数值求解器,valarray 几乎是强制性的。