繁体   English   中英

C++ valarray 与向量

[英]C++ valarray vs. vector

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

valarray是一种在错误的时间出生在错误的地方的孤儿。 这是一种优化尝试,特别是针对在编写时用于重型数学的机器——特别是像 Crays 这样的向量处理器。

对于向量处理器,您通常想要做的是将单个操作应用于整个数组,然后将下一个操作应用于整个数组,依此类推,直到完成您需要做的所有事情。

但是,除非您正在处理相当小的数组,否则它在缓存时往往效果不佳。 在大多数现代机器上,您通常更喜欢(在可能的范围内)加载数组的一部分,对其执行所有操作,然后移至数组的下一部分。

valarray还应该消除任何混叠的可能性,这(至少在理论上)让编译器提高速度,因为在寄存器中存储值更自由。 然而,实际上,我完全不确定任何真正的实现会在多大程度上利用这一点。 我怀疑这是一个先有鸡还是先有蛋的问题——如果没有编译器支持,它就不会流行,而且只要它不流行,没有人会费心去开发他们的编译器来支持它。

还有一个令人眼花缭乱的(字面意思)数组与 valarray 一起使用的辅助类。 您可以使用sliceslice_arraygslicegslice_array来处理valarray ,并使其表现得像一个多维数组。 您还可以使用mask_array来“屏蔽”操作(例如,将 x 中的项目添加到 y,但仅限于 z 非零的位置)。 为了不只是简单地使用valarray ,您必须了解很多关于这些辅助类的知识,其中一些非常复杂,而且似乎(至少对我而言)没有一个有很好的文档记录。

底线:虽然它有辉煌的时刻,并且可以非常整洁地做一些事情,但也有一些很好的理由让它(并且几乎肯定会保持)默默无闻。

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

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

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

Valarrays 也有允许你以相当简单的方式将它们切片的类,尽管标准的那部分可能需要更多的工作。 调整它们的大小是破坏性的,而且它们缺乏迭代器。

因此,如果它是您正在使用的数字并且便利性并不是那么重要,请使用 valarrays。 否则,向量就方便多了。

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

ISTR 认为它没有从标准中删除的主要原因是没有人花时间彻底评估该问题并编写提案以将其删除。

但是请记住,这一切都是依稀记得的道听途说。 对此持保留态度,希望有人纠正或确认这一点。

我知道 valarrays 有一些语法糖

我不得不说,我认为std::valarrays没有太多的语法糖。 语法不同,但我不会称这种差异为“糖”。 API 很奇怪。 The C++ Programming Language中关于std::valarray的部分提到了这个不寻常的 API 以及这样一个事实,因为std::valarray应该是高度优化的,你在使用它们时得到的任何错误消息都可能是不直观的。

出于好奇,大约一年前,我将std::valarraystd::vector对比。 我不再有代码或精确的结果(尽管编写自己的代码应该不难)。 使用 GCC 在将std::valarray用于简单数学时确实获得了一些性能优势,但对于我的实现来计算标准偏差(当然,就数学而言,标准偏差并不那么复杂)。 我怀疑对大型 std::vector中的每个项目的操作比对 std::valarray的操作更适合缓存。 注意,根据musiphil 的建议,我设法从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 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 应该让一些 FORTRAN 向量处理的优点在 C++ 上消失。 不知何故,必要的编译器支持从未真正发生过。

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

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

我发现了 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您可以std::valarray使用标准数学符号,例如v1 = a*v2 + v3 除非您定义自己的运算符,否则这对于向量是不可能的。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM